Merge pull request #2894 from moodlehq/integration

Integration
main
Juan Leyva 2021-07-19 15:32:59 +02:00 committed by GitHub
commit a362711864
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5124 changed files with 300639 additions and 352029 deletions

1
.eslintignore 100644
View File

@ -0,0 +1 @@
*.js

301
.eslintrc.js 100644
View File

@ -0,0 +1,301 @@
const appConfig = {
env: {
browser: true,
es6: true,
node: true,
},
plugins: [
'@typescript-eslint',
'header',
'jsdoc',
'prefer-arrow',
'promise',
],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
'plugin:@angular-eslint/recommended',
'plugin:promise/recommended',
],
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
sourceType: 'module',
},
reportUnusedDisableDirectives: true,
rules: {
'@angular-eslint/component-class-suffix': ['error', { suffixes: ['Component', 'Page'] }],
'@angular-eslint/no-output-on-prefix': 'off',
'@typescript-eslint/adjacent-overload-signatures': 'error',
'@typescript-eslint/ban-types': [
'error',
{
types: {
Boolean: {
message: 'Use \'boolean\' instead.',
},
Number: {
message: 'Use \'number\' instead.',
},
String: {
message: 'Use \'string\' instead.',
},
Object: {
message: 'Use {} instead.',
},
},
},
],
'@typescript-eslint/explicit-member-accessibility': [
'error',
{
accessibility: 'no-public',
},
],
'@typescript-eslint/explicit-module-boundary-types': [
'error',
{
allowArgumentsExplicitlyTypedAsAny: true,
},
],
'@typescript-eslint/indent': [
'error',
4,
{
SwitchCase: 1,
ignoredNodes: [
'ClassProperty *',
],
},
],
'@typescript-eslint/lines-between-class-members': [
'error',
'always',
{
exceptAfterSingleLine: true,
},
],
'@typescript-eslint/member-delimiter-style': [
'error',
{
multiline: {
delimiter: 'semi',
requireLast: true,
},
singleline: {
delimiter: 'semi',
requireLast: false,
},
},
],
'@typescript-eslint/member-ordering': [
'error',
{
default:
{
order: 'as-written',
},
}
],
'@typescript-eslint/naming-convention': [
'error',
{
selector: 'property',
modifiers: ['readonly'],
format: ['UPPER_CASE'],
},
{
selector: 'property',
format: ['camelCase'],
},
{
selector: 'property',
modifiers: ['private'],
format: ['camelCase'],
leadingUnderscore: 'allow',
},
],
'@typescript-eslint/no-empty-function': 'error',
'@typescript-eslint/no-empty-interface': 'off',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-inferrable-types': [
'error',
{
ignoreParameters: true,
},
],
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-redeclare': 'error',
'@typescript-eslint/no-this-alias': 'error',
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/quotes': [
'error',
'single',
],
'@typescript-eslint/semi': [
'error',
'always',
],
'@typescript-eslint/type-annotation-spacing': 'error',
'@typescript-eslint/unified-signatures': 'error',
'header/header': [
2,
'line',
[
' (C) Copyright 2015 Moodle Pty Ltd.',
'',
' 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.',
],
1,
],
'promise/catch-or-return': [
'warn',
{
allowFinally: true,
terminationMethod: ['catch', 'finally'],
},
],
'arrow-body-style': ['error', 'as-needed'],
'array-bracket-spacing': ['error', 'never'],
'comma-dangle': ['error', 'always-multiline'],
'constructor-super': 'error',
'curly': 'error',
'eol-last': 'error',
'function-call-argument-newline': ['error', 'consistent'],
'function-paren-newline': ['error', 'multiline-arguments'],
'id-blacklist': [
'error',
'any',
'Number',
'number',
'String',
'string',
'Boolean',
'boolean',
'Undefined',
'undefined',
],
'id-match': 'error',
'jsdoc/check-alignment': 'error',
'jsdoc/newline-after-description': 'error',
'linebreak-style': [
'error',
'unix',
],
'max-len': [
'error',
{
code: 132,
},
],
'new-parens': 'error',
'no-bitwise': 'error',
'no-cond-assign': 'error',
'no-console': 'error',
'no-debugger': 'error',
'no-duplicate-case': 'error',
'no-duplicate-imports': 'error',
'no-empty': 'error',
'no-eval': 'error',
'no-fallthrough': 'off',
'no-invalid-this': 'error',
'no-irregular-whitespace': 'error',
'no-multiple-empty-lines': ['error', { "max": 1 }],
'no-new-wrappers': 'error',
'no-sequences': 'error',
'no-trailing-spaces': 'error',
'no-unused-labels': 'error',
'no-var': 'error',
'object-curly-spacing': ['error', 'always'],
'one-var': ['error', 'never'],
'padded-blocks': [
'error',
{
classes: 'always',
blocks: 'never',
switches: 'never',
},
],
'padding-line-between-statements': [
'error',
{
blankLine: 'always',
prev: '*',
next: 'return',
},
],
'prefer-arrow/prefer-arrow-functions': [
'error',
{
singleReturnOnly: true,
allowStandaloneDeclarations: true,
},
],
'prefer-const': 'error',
'prefer-spread': 'off',
'quote-props': [
'error',
'consistent-as-needed',
],
'spaced-comment': [
'error',
'always',
{
markers: [
'/',
],
},
],
'use-isnan': 'error',
'yoda': 'error',
},
};
var testsConfig = Object.assign({}, appConfig);
testsConfig['rules']['padded-blocks'] = [
'error',
{
classes: 'always',
switches: 'never',
},
];
testsConfig['plugins'].push('jest');
testsConfig['extends'].push('plugin:jest/recommended');
module.exports = {
root: true,
overrides: [
Object.assign({ files: ['*.ts'] }, appConfig),
Object.assign({ files: ['*.test.ts'] }, testsConfig),
{
files: ['*.html'],
extends: ['plugin:@angular-eslint/template/recommended'],
rules: {
'max-len': ['warn', { code: 140 }],
'@angular-eslint/template/accessibility-valid-aria': 'warn',
'@angular-eslint/template/accessibility-alt-text': 'error',
'@angular-eslint/template/accessibility-elements-content': 'error',
'@angular-eslint/template/accessibility-label-for': 'error',
'@angular-eslint/template/no-positive-tabindex': 'error',
'@angular-eslint/template/accessibility-table-scope': 'error',
'@angular-eslint/template/accessibility-valid-aria': 'error',
},
},
{
files: ['*.component.ts'],
extends: ['plugin:@angular-eslint/template/process-inline-templates'],
},
],
};

8
.gitattributes vendored
View File

@ -1,8 +0,0 @@
# This file has been retrieved from angular repository.
# Auto detect text files and perform LF normalization
* text=auto
# JS and TS files must always use LF for tools to work
*.js eol=lf
*.ts eol=lf

65
.github/scripts/functions.sh vendored 100644
View File

@ -0,0 +1,65 @@
#!/bin/bash
function check_success_exit {
if [ $? -ne 0 ]; then
print_error "$1"
exit 1
elif [ "$#" -gt 1 ]; then
print_ok "$2"
fi
}
function check_success {
if [ $? -ne 0 ]; then
print_error "$1"
elif [ "$#" -gt 1 ]; then
print_ok "$2"
fi
}
function print_success {
if [ $? -ne 0 ]; then
print_message "$1"
$3=0
else
print_ok "$2"
fi
}
function print_error {
echo " ERROR: $1"
}
function print_ok {
echo " OK: $1"
echo
}
function print_message {
echo "-------- $1"
echo
}
function print_title {
stepnumber=$(($stepnumber + 1))
echo
echo "$stepnumber $1"
echo '=================='
}
function telegram_notify {
if [ ! -z $TELEGRAM_APIKEY ] && [ ! -z $TELEGRAM_CHATID ] ; then
MESSAGE="Travis error: $1%0ABranch: $TRAVIS_BRANCH%0ARepo: $TRAVIS_REPO_SLUG"
URL="https://api.telegram.org/bot$TELEGRAM_APIKEY/sendMessage"
curl -s -X POST $URL -d chat_id=$TELEGRAM_CHATID -d text="$MESSAGE"
fi
}
function notify_on_error_exit {
if [ $? -ne 0 ]; then
print_error "$1"
telegram_notify "$1"
exit 1
fi
}

14
.github/scripts/mirror.sh vendored 100755
View File

@ -0,0 +1,14 @@
#!/bin/bash
source "./.github/scripts/functions.sh"
BRANCH=${GITHUB_REF##*/}
if [ -z $GIT_TOKEN ] || [ -z $BRANCH ] || [ $GITHUB_REPOSITORY != 'moodlehq/moodleapp' ]; then
print_error "Env vars not correctly defined"
exit 1
fi
git remote add mirror https://$GIT_TOKEN@github.com/moodlemobile/moodleapp.git
git push -f mirror HEAD:$BRANCH
notify_on_error_exit "MIRROR: Unsuccessful mirror, stopping..."
git push -f mirror --tags
notify_on_error_exit "MIRROR: Unsuccessful mirror tags, stopping..."

24
.github/scripts/prepare.sh vendored 100755
View File

@ -0,0 +1,24 @@
#!/bin/bash
source "./.github/scripts/functions.sh"
if [ -z $GIT_TOKEN ] || [ $GITHUB_REPOSITORY != 'moodlemobile/moodleapp' ]; then
print_error "Env vars not correctly defined"
exit 1
fi
print_title "Run prepare scripts"
# TODO Change branch name.
git clone --depth 1 --single-branch --branch ionic5 https://$GIT_TOKEN@github.com/moodlemobile/apps-scripts.git ../scripts
cp ../scripts/*.sh scripts/
if [ ! -f scripts/prepare.sh ]; then
print_error "Prepare file not found"
exit 1
fi
print_title 'Prepare Build'
./scripts/prepare.sh
if [ $? -ne 0 ]; then
exit 1
fi

19
.github/workflows/migration.yml vendored 100644
View File

@ -0,0 +1,19 @@
name: Migration checks
on: workflow_dispatch
jobs:
checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v1
with:
node-version: '12.x'
- run: npm ci
- run: result=$(find src -type f -iname '*.html' -exec sh -c 'cat {} | tr "\n" " " | grep -Eo "class=\"[^\"]+\"[^>]+class=\"" ' \; | wc -l); test $result -eq 0
- run: npm install -D @ionic/v4-migration-tslint
- run: npx tslint -c ionic-migration.json -p tsconfig.json

20
.github/workflows/mirror.yml vendored 100644
View File

@ -0,0 +1,20 @@
name: Mirror
on:
push:
branches: [ master, integration ]
jobs:
mirror:
if: github.repository == 'moodlehq/moodleapp'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
persist-credentials: false
fetch-depth: 0
- name: Mirror the branch and tags
env:
GIT_TOKEN: ${{ secrets.GIT_TOKEN }}
run: ./.github/scripts/mirror.sh

20
.github/workflows/prepare.yml vendored 100644
View File

@ -0,0 +1,20 @@
name: Prepare
on:
push:
branches: [ master, integration, freemium-master ]
jobs:
prepare:
if: github.repository == 'moodlemobile/moodleapp'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
persist-credentials: false
fetch-depth: 0
- name: Prepare builds
env:
GIT_TOKEN: ${{ secrets.GIT_TOKEN }}
run: ./.github/scripts/prepare.sh

59
.github/workflows/testing.yml vendored 100644
View File

@ -0,0 +1,59 @@
name: Testing
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v1
with:
node-version: '12.x'
- name: Install npm packages
run: npm ci
- name: Check langindex
run: |
result=$(cat scripts/langindex.json | grep \"TBD\" | wc -l); test $result -eq 0
if [ $result -ne 0 ]; then
echo "There are lang strings to be decided on langindex.json"
exit 1
fi
gulp
langcount=`jq -r '. | length' src/assets/lang/en.json`
freemiumcount=`jq 'keys' src/assets/lang/en.json | grep "freemium\." | wc -l | xargs`
allcount=$(($langcount - $freemiumcount))
langindexcount=`jq -r '. | length' scripts/langindex.json`
if [ $allcount -ne $langindexcount ]; then
echo "Lang file has $langcount ($freemiumcount) while langindex $langindexcount"
exit 1
fi
langkeys=`jq -r 'keys[]' src/assets/lang/en.json | grep -v "freemium\."`
langindex=`jq -r 'keys[]' scripts/langindex.json`
found=0
for i in $langkeys; do
skip=
for j in $langindex; do
if [ "$i" == "$j" ]; then
skip=1
break;
fi
done
[[ -n $skip ]] || { echo "$i key not found"; found=$(($found + 1)); }
done
if [ $found -ne 0 ]; then
echo "Found $found missing langkeys"
exit 1
fi
- name: Run Linter
run: npm run lint
- name: Run tests
run: npm run test:ci
- name: Production builds
run: npm run build:prod
- name: JavaScript code compatibility
run: result=$(npx check-es-compat www/*.js 2> /dev/null | grep -v -E "Array\.prototype\.includes|Promise\.prototype\.finally|String\.prototype\.(matchAll|trimRight)|globalThis" | grep -Po "(?<=error).*?(?=\s+ecmascript)" | wc -l); test $result -eq 0

60
.gitignore vendored
View File

@ -3,47 +3,35 @@
*~
*.sw[mnpcod]
*.log
.tmp
*.tmp
*.tmp.*
log.txt
*.sublime-project
*.sublime-workspace
.vscode/
npm-debug.log*
.idea/
.sourcemaps/
.sass-cache/
.tmp/
.versions/
coverage/
dist/
node_modules/
tmp/
temp/
platforms/
/plugins/
/plugins/android.json
/plugins/ios.json
resources/android/icon
resources/android/splash
resources/ios/icon
resources/ios/splash
resources/windows/icon
resources/windows/splash
config.xml
www/
!www/README.md
$RECYCLE.BIN/
.DS_Store
Thumbs.db
UserInterfaceState.xcuserstate
$RECYCLE.BIN/
e2e/build
/desktop/*
!/desktop/assets/
!/desktop/electron.js
src/configconstants.ts
.moodleapp-dev-config
*.log
log.txt
npm-debug.log*
/.idea
/.ionic
/.sass-cache
/.sourcemaps
/.versions
/coverage
/dist
/node_modules
/platforms
/plugins
/www
/src/assets/lib
/moodle.config.*.json
!/moodle.config.example.json
/src/assets/lang/*
/src/assets/env.json

View File

@ -1,19 +1,6 @@
os: linux
dist: bionic
group: edge
language: node_js
node_js: 11
php: 7.1
android:
components:
- tools
- platform-tools
- build-tools-29.0.3
- android-28
- extra-google-google_play_services
- extra-google-m2repository
- extra-android-m2repository
dist: trusty
node_js: 12
git:
depth: 3
@ -23,42 +10,52 @@ before_cache:
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
cache:
npm: true
directories:
- $HOME/.npm
- $HOME/.gradle/caches/
- $HOME/.gradle/wrapper/
- $HOME/.android/build-cache
before_install:
- nvm install 12
- node --version
- npm --version
- nvm --version
- npm ci
- npm install npm@^6 -g
before_script:
- npm install npm@latest -g
- gulp
- npx gulp
script:
- scripts/build.sh
jobs:
include:
- stage: check
if: NOT branch =~ /(master|integration|desktop)$/ AND env(DEPLOY) IS blank
script: npm run build --bailOnLintError true --typeCheckOnLint true
- stage: mirror
if: branch IN (master, integration, desktop) AND repo = moodlehq/moodleapp AND type != cron
script: scripts/mirror.sh
- stage: prepare
if: branch =~ /(master|^integration)$/ AND env(PREPARE) IS present AND env(PREPARE) = 1 AND type != cron AND tag IS blank
script: scripts/aot.sh
- stage: build
name: "Build Android"
if: env(DEPLOY) IS present AND type != cron AND ((env(DEPLOY) = 1 AND tag IS blank) OR (env(DEPLOY) = 2 AND tag IS present))
dist: trusty
if: env(DEPLOY) = 1 OR (env(DEPLOY) = 2 AND tag IS NOT blank)
language: android
env:
- BUILD_PLATFORM='android'
before_install:
- nvm install 11
- node --version
- npm --version
- nvm --version
- npm ci
- npm install -g gulp
script: scripts/aot.sh
android:
components:
- tools
- platform-tools
- build-tools-29.0.3
- android-29
- extra-google-google_play_services
- extra-google-m2repository
- extra-android-m2repository
addons:
apt:
packages:
- libsecret-1-dev
- stage: build
name: "Build iOS"
language: node_js
if: env(BUILD_IOS) = 1 AND (env(DEPLOY) = 1 OR (env(DEPLOY) = 2 AND tag IS NOT blank))
os: osx
osx_image: xcode12.5
- stage: test
name: "End to end tests (mod_forum and mod_messages)"
services:
@ -66,20 +63,14 @@ jobs:
if: type = cron
script: scripts/test_e2e.sh "@app&&@mod_forum" "@app&&@mod_messages"
- stage: test
name: "End to end tests (mod_data and mod_survey)"
name: "End to end tests (mod_course, core_course and mod_courses)"
services:
- docker
if: type = cron
script: scripts/test_e2e.sh "@app&&@mod_data" "@app&&@mod_survey"
- stage: test
name: "End to end tests (mod_comments, mod_course, core_course and mod_courses)"
services:
- docker
if: type = cron
script: scripts/test_e2e.sh "@app&&@mod_comments" "@app&&@mod_course" "@app&&@core_course" "@app&&@mod_courses"
script: scripts/test_e2e.sh "@app&&@mod_course" "@app&&@core_course" "@app&&@mod_courses"
- stage: test
name: "End to end tests (others)"
services:
- docker
if: type = cron
script: scripts/test_e2e.sh "@app&&~@mod_forum&&~@mod_messages&&~@mod_comments&&~@mod_data&&~@mod_survey&&~@mod_course&&~@core_course&&~@mod_courses"
script: scripts/test_e2e.sh "@app&&~@mod_forum&&~@mod_messages&&~@mod_course&&~@core_course&&~@mod_courses"

35
.vscode/launch.json vendored 100644
View File

@ -0,0 +1,35 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Jest All",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"args": ["--runInBand"],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"disableOptimisticBPs": true,
"windows": {
"program": "${workspaceFolder}/node_modules/jest/bin/jest",
}
},
{
"type": "node",
"request": "launch",
"name": "Jest Current File",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"args": [
"${fileBasenameNoExtension}",
"--config",
"jest.config.js"
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"disableOptimisticBPs": true,
"windows": {
"program": "${workspaceFolder}/node_modules/jest/bin/jest",
}
}
]
}

113
.vscode/moodle.code-snippets vendored 100644
View File

@ -0,0 +1,113 @@
{
"[Moodle] Component class": {
"scope": "typescript",
"prefix": "macomponent",
"body": [
"import { Component } from '@angular/core';",
"",
"@Component({",
" selector: '$2${TM_FILENAME_BASE}',",
" templateUrl: '$2${TM_FILENAME_BASE}.html',",
"})",
"export class ${1:${TM_FILENAME_BASE}}Component {",
"",
" $0",
"",
"}",
""
],
"description": "[Moodle] Create a Component class"
},
"[Moodle] Page class": {
"scope": "typescript",
"prefix": "mapage",
"body": [
"import { Component } from '@angular/core';",
"",
"@Component({",
" selector: 'page-$2${TM_FILENAME_BASE}',",
" templateUrl: '${TM_FILENAME_BASE}.html',",
"})",
"export class ${1:${TM_FILENAME_BASE}}Page {",
"",
" $0",
"",
"}",
""
],
"description": "[Moodle] Create a Page class"
},
"[Moodle] Module class": {
"scope": "typescript",
"prefix": "mamodule",
"body": [
"import { NgModule } from '@angular/core';",
"",
"@NgModule({",
" $0",
"})",
"export class ${1}Module {}",
""
],
"description": "[Moodle] Create a Module class"
},
"[Moodle] Lazy Module class": {
"scope": "typescript",
"prefix": "malazymodule",
"body": [
"import { NgModule } from '@angular/core';",
"import { RouterModule, Routes } from '@angular/router';",
"",
"const routes: Routes = [",
" $0",
"];",
"",
"@NgModule({",
" imports: [",
" RouterModule.forChild(routes),",
" ],",
"})",
"export class ${1}LazyModule {}",
""
],
"description": "[Moodle] Create a Lazy Module class"
},
"[Moodle] Service Singleton": {
"scope": "typescript",
"prefix": "massingleton",
"body": [
"import { Injectable } from '@angular/core';",
"import { makeSingleton } from '@singletons';",
"",
"/**",
" * $2.",
" */",
"@Injectable({ providedIn: 'root' })",
"export class ${1:${TM_FILENAME_BASE}}Service {",
"",
" $0",
"",
"}",
"",
"export const ${1:${TM_FILENAME_BASE}} = makeSingleton(${1:${TM_FILENAME_BASE}}Service);",
""
],
"description": "[Moodle] Create a Service Singleton"
},
"[Moodle] Singleton": {
"scope": "typescript",
"prefix": "masingleton",
"body": [
"/**",
" * Singleton$2.",
" */",
"export class ${1:${TM_FILENAME_BASE}} {",
"",
" $0",
"",
"}",
""
],
"description": "[Moodle] Create a Pure Singleton"
},
}

8
.vscode/settings.json vendored 100644
View File

@ -0,0 +1,8 @@
{
"files.associations": {
"moodle.config.json": "jsonc",
"moodle.config.*.json": "jsonc",
},
}

View File

@ -1,27 +1,22 @@
# This image is based on the fat node 11 image.
# We require fat images as neither alpine, or slim, include git binaries.
FROM node:11
# Port 8100 for ionic dev server.
EXPOSE 8100
# Port 35729 is the live-reload server.
EXPOSE 35729
# Port 53703 is the Chrome dev logger port.
EXPOSE 53703
## BUILD STAGE
FROM node:14 as build-stage
WORKDIR /app
# Prepare node dependencies
RUN apt-get update && apt-get install libsecret-1-0 -y
COPY package*.json ./
RUN npm ci
# Build source
ARG build_command="npm run build:prod"
COPY . /app
RUN ${build_command}
# Install npm libraries.
RUN npm install && rm -rf /root/.npm
## SERVE STAGE
FROM nginx:alpine as serve-stage
# Run gulp before starting.
RUN npx gulp
# 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
CMD ["npm", "run", "ionic:serve"]
# Copy assets & config
COPY --from=build-stage /app/www /usr/share/nginx/html
COPY ./nginx.conf /etc/nginx/conf.d/default.conf
HEALTHCHECK --interval=10s --timeout=4s CMD curl -f http://localhost/assets/env.json || exit 1

202
LICENSE
View File

@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

13
NOTICE
View File

@ -1,13 +0,0 @@
(C) Copyright 2015 Moodle Pty Ltd.
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.

View File

@ -1,17 +0,0 @@
Package updates known problems
=================
@ionic/app-scripts 3.2.3 shows error Cannot find type definition file for '@types'. on ngc build.
com-darryncampbell-cordova-plugin-intent 2.0.0 onwards needs Android X Support. Unsupported on PGB.
typescript is needed to be less than 2.7 for @angular/compiler-cli
cordova-plugin-ionic-keyboard has problems on greater versions than 2.1.3
jszip has problems with "lie" dependency on greater versions than 3.1
promise.prototype.finally has problems on greater versions than 3.1
cordova-ios: should remain on 5.1 because of: https://github.com/apache/cordova-ios/pull/801

138
angular.json 100644
View File

@ -0,0 +1,138 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"defaultProject": "app",
"newProjectRoot": "projects",
"projects": {
"app": {
"root": "",
"sourceRoot": "src",
"projectType": "application",
"prefix": "app",
"schematics": {},
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "www",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"assets": [
{
"glob": "**/*",
"input": "src/assets",
"output": "assets"
},
{
"glob": "**/*.svg",
"input": "node_modules/ionicons/dist/ionicons/svg",
"output": "./svg"
}
],
"styles": [
{
"input": "src/theme/theme.scss"
}
],
"scripts": []
},
"configurations": {
"production": {
"optimization": {
"scripts": false,
"styles": true
},
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "50mb",
"maximumError": "100mb"
}
]
},
"ci": {
"progress": false
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "app:build",
"disableHostCheck": true,
"port": 8100
},
"configurations": {
"production": {
"browserTarget": "app:build:production"
},
"ci": {
"progress": false
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "app:build"
}
},
"lint": {
"builder": "@angular-eslint/builder:lint",
"options": {
"lintFilePatterns": [
"src/**/*.ts",
"src/core/**/*.html",
"src/addons/**/*.html"
]
}
},
"ionic-cordova-build": {
"builder": "@ionic/angular-toolkit:cordova-build",
"options": {
"browserTarget": "app:build"
},
"configurations": {
"production": {
"browserTarget": "app:build:production"
}
}
},
"ionic-cordova-serve": {
"builder": "@ionic/angular-toolkit:cordova-serve",
"options": {
"cordovaBuildTarget": "app:ionic-cordova-build",
"devServerTarget": "app:serve"
},
"configurations": {
"production": {
"cordovaBuildTarget": "app:ionic-cordova-build:production",
"devServerTarget": "app:serve:production"
}
}
}
}
}
},
"cli": {
"defaultCollection": "@ionic/angular-toolkit"
},
"schematics": {
"@ionic/angular-toolkit:component": {
"styleext": "scss"
},
"@ionic/angular-toolkit:page": {
"styleext": "scss"
}
}
}

15
browserslist 100644
View File

@ -0,0 +1,15 @@
# This file indicates the platforms supported. These targets will only be polyfilled automatically in CSS,
# JavaScript polyfills have to be configured manually in `src/polyfills.ts`.
#
# In order to see which browsers are supported you can run `npx browserslist` (or visit https://browserslist.dev/).
#
# Keep in mind that not all versions of all browsers will be displayed, here's all the versions available:
# https://caniuse.com/ciu/comparison
#
# From time to time, you'll want to update the database by running `npx browserslist@latest --update-db`.
#
# More info: https://github.com/browserslist/browserslist
Android >= 5
iOS >= 11
Chrome >= 61

View File

@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<widget android-versionCode="39400" id="com.moodle.moodlemobile" ios-CFBundleVersion="3.9.4.0" version="3.9.4" versionCode="39400" 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 android-versionCode="39500" id="com.moodle.moodlemobile" ios-CFBundleVersion="3.9.5.0" version="3.9.5" versionCode="39500" 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>
@ -31,6 +31,7 @@
<preference name="BackupWebStorage" value="none" />
<preference name="ScrollEnabled" value="false" />
<preference name="KeyboardDisplayRequiresUserAction" value="false" />
<preference name="HideKeyboardFormAccessoryBar" value="false" />
<preference name="AllowInlineMediaPlayback" value="true" />
<preference name="LoadUrlTimeoutValue" value="60000" />
<preference name="load-url-timeout" value="60000" />
@ -38,9 +39,10 @@
<preference name="SplashScreenDelay" value="15000" />
<preference name="SplashMaintainAspectRatio" value="true" />
<preference name="SplashShowOnlyFirstTime" value="false" />
<preference name="android-minSdkVersion" value="19" />
<preference name="android-targetSdkVersion" value="29" />
<preference name="android-minSdkVersion" value="22" />
<preference name="android-targetSdkVersion" value="30" />
<preference name="AndroidPersistentFileLocation" value="Compatibility" />
<preference name="AndroidInsecureFileModeEnabled" value="true" />
<preference name="CustomURLSchemePluginClearsAndroidIntent" value="true" />
<preference name="iosPersistentFileLocation" value="Compatibility" />
<preference name="iosScheme" value="moodleappfs" />
@ -222,6 +224,16 @@
<config-file parent="/*" target="AndroidManifest.xml">
<uses-feature android:name="android.hardware.bluetooth" android:required="false" />
</config-file>
<config-file parent="/*" target="AndroidManifest.xml">
<queries>
<intent>
<action android:name="android.media.action.IMAGE_CAPTURE" />
</intent>
<intent>
<action android:name="android.intent.action.GET_CONTENT" />
</intent>
</queries>
</config-file>
</platform>
<platform name="ios">
<resource-file src="GoogleService-Info.plist" />
@ -244,7 +256,12 @@
<true />
</edit-config>
<edit-config file="*-Info.plist" mode="merge" target="CFBundleShortVersionString">
<string>3.9.4</string>
<string>3.9.5</string>
</edit-config>
<edit-config file="*-Info.plist" mode="overwrite" target="CFBundleLocalizations">
<array>
<string>en</string>
</array>
</edit-config>
<config-file parent="FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED" target="*-Info.plist">
<string>YES</string>
@ -268,6 +285,9 @@
<config-file parent="UIRequiresFullScreen" target="*-Info.plist">
<false />
</config-file>
<config-file parent="NSCrossWebsiteTrackingUsageDescription" target="*-Info.plist">
<string>This app needs third party cookies to correctly render embedded content from the Moodle site.</string>
</config-file>
<config-file parent="CFBundleDocumentTypes" target="*-Info.plist">
<array>
<dict>

View File

@ -1,48 +0,0 @@
// New copy task for font files and config.json.
module.exports = {
// Override Ionic copyFonts task to exclude Roboto and Noto fonts.
copyFonts: {
src: ['{{ROOT}}/node_modules/ionicons/dist/fonts/**/*'],
dest: '{{WWW}}/assets/fonts'
},
copyFontAwesome: {
src: ['{{ROOT}}/node_modules/font-awesome/fonts/**/*'],
dest: '{{WWW}}/assets/fonts'
},
copyConfig: {
src: ['{{ROOT}}/src/config.json'],
dest: '{{WWW}}/'
},
copyMathJaxMain: {
src: ['{{ROOT}}/node_modules/mathjax/MathJax.js'],
dest: '{{WWW}}/lib/mathjax'
},
copyMathJaxExtensions: {
src: ['{{ROOT}}/node_modules/mathjax/extensions/**/*'],
dest: '{{WWW}}/lib/mathjax/extensions'
},
copyMathJaxElement: {
src: ['{{ROOT}}/node_modules/mathjax/jax/element/**/*'],
dest: '{{WWW}}/lib/mathjax/jax/element'
},
copyMathJaxInput: {
src: ['{{ROOT}}/node_modules/mathjax/jax/input/**/*'],
dest: '{{WWW}}/lib/mathjax/jax/input'
},
copyMathJaxSVGOutput: {
src: ['{{ROOT}}/node_modules/mathjax/jax/output/SVG/**/*'],
dest: '{{WWW}}/lib/mathjax/jax/output/SVG'
},
copyMathJaxPreviewHTMLOutput: {
src: ['{{ROOT}}/node_modules/mathjax/jax/output/PreviewHTML/**/*'],
dest: '{{WWW}}/lib/mathjax/jax/output/PreviewHTML'
},
copyMathJaxLocalization: {
src: ['{{ROOT}}/node_modules/mathjax/localization/**/*'],
dest: '{{WWW}}/lib/mathjax/localization'
},
copyH5P: {
src: ['{{ROOT}}/src/core/h5p/assets/**/*'],
dest: '{{WWW}}/h5p/'
},
};

View File

@ -1,15 +0,0 @@
// Adding Font Awesome to includePaths
module.exports = {
includePaths: [
'node_modules/ionic-angular/themes',
'node_modules/ionicons/dist/scss',
'node_modules/ionic-angular/fonts',
'node_modules/font-awesome/scss'
],
includeFiles: [
/\.(s(c|a)ss)$/i
],
excludeFiles: [
/\.(wp).(scss)$/i
]
};

View File

@ -1,19 +0,0 @@
// Check https://github.com/mishoo/UglifyJS2/tree/harmony#minify-options-structure
module.exports = {
/**
* mangle: uglify 2's mangle option
*/
mangle: {
keep_classnames: true,
keep_fnames: true
},
/**
* compress: uglify 2's compress option
*/
compress: {
toplevel: true,
pure_getters: true
},
keep_classnames: true,
keep_fnames: true
}

View File

@ -1,43 +0,0 @@
const { resolve } = require('path');
const webpackMerge = require('webpack-merge');
const { dev, prod } = require('@ionic/app-scripts/config/webpack.config');
const customConfig = {
resolve: {
alias: {
'@addon': resolve('./src/addon'),
'@app': resolve('./src/app'),
'@classes': resolve('./src/classes'),
'@core': resolve('./src/core'),
'@providers': resolve('./src/providers'),
'@components': resolve('./src/components'),
'@directives': resolve('./src/directives'),
'@pipes': resolve('./src/pipes'),
'@singletons': resolve('./src/singletons'),
}
},
externals: [
(function () {
var IGNORES = ["fs","child_process","electron","path","assert","cluster","crypto","dns","domain","events","http","https","net","os","process","punycode","querystring","readline","repl","stream","string_decoder","tls","tty","dgram","url","util","v8","vm","zlib"];
return function (context, request, callback) {
if (IGNORES.indexOf(request) >= 0) {
return callback(null, "require('" + request + "')");
}
return callback();
};
})()
],
module: {
loaders: [
{
test: /\.node$/,
use: 'node-loader'
}
]
}
};
module.exports = {
dev: webpackMerge(dev, customConfig),
prod: webpackMerge(prod, customConfig),
}

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.inherit</key>
<true/>
</dict>
</plist>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
</dict>
</plist>

View File

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>2NU57U5PAW.com.moodle.moodledesktop</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.device.audio-input</key>
<true/>
</dict>
</plist>

View File

@ -1,38 +0,0 @@
#!/bin/bash
#
# Script to sign macOSX pkg.
# https://www.electronjs.org/docs/tutorial/mac-app-store-submission-guide
#
# Name of your app.
APP="Moodle Desktop"
# The name of certificates you requested.
APP_KEY="3rd Party Mac Developer Application: Moodle Pty Ltd (2NU57U5PAW)"
INSTALLER_KEY="3rd Party Mac Developer Installer: Moodle Pty Ltd (2NU57U5PAW)"
BASEPATH="desktop/dist/mas"
# The path of your app to sign.
APP_PATH="${BASEPATH}/${APP}.app"
# The path to the location you want to put the signed package.
RESULT_PATH="${BASEPATH}/${APP}.pkg"
# The path of your plist files.
CHILD_PLIST="desktop/assets/mac/child.plist"
PARENT_PLIST="desktop/assets/mac/parent.plist"
LOGINHELPER_PLIST="desktop/assets/mac/loginhelper.plist"
FRAMEWORKS_PATH="$APP_PATH/Contents/Frameworks"
codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A/Electron Framework"
codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A/Libraries/libffmpeg.dylib"
codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/Electron Framework.framework/Versions/A/Libraries/libnode.dylib"
codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/Electron Framework.framework"
codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper.app/Contents/MacOS/$APP Helper"
codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$FRAMEWORKS_PATH/$APP Helper.app/"
codesign -s "$APP_KEY" -f --entitlements "$LOGINHELPER_PLIST" "$APP_PATH/Contents/Library/LoginItems/$APP Login Helper.app/Contents/MacOS/$APP Login Helper"
codesign -s "$APP_KEY" -f --entitlements "$LOGINHELPER_PLIST" "$APP_PATH/Contents/Library/LoginItems/$APP Login Helper.app/"
codesign -s "$APP_KEY" -f --entitlements "$CHILD_PLIST" "$APP_PATH/Contents/MacOS/$APP"
codesign -s "$APP_KEY" -f --entitlements "$PARENT_PLIST" "$APP_PATH"
productbuild --component "$APP_PATH" /Applications --sign "$INSTALLER_KEY" "$RESULT_PATH"

View File

@ -1,44 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities">
<Identity Name="3312ADB7.MoodleDesktop"
ProcessorArchitecture="x64"
Publisher="CN=33CDCDF6-1EB5-4827-9897-ED25C91A32F6"
Version="3.9.4.0" />
<Properties>
<DisplayName>Moodle Desktop</DisplayName>
<PublisherDisplayName>Moodle Pty Ltd.</PublisherDisplayName>
<Description>The official app for Moodle.</Description>
<Logo>assets\StoreLogo.png</Logo>
</Properties>
<Resources>
<Resource Language="en" />
</Resources>
<Dependencies>
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.14316.0" MaxVersionTested="10.0.14316.0" />
</Dependencies>
<Capabilities>
<rescap:Capability Name="runFullTrust"/>
</Capabilities>
<Applications>
<Application Id="com.moodle.moodlemobile" Executable="app\Moodle Desktop.exe" EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements
BackgroundColor="#464646"
DisplayName="Moodle Desktop"
Square150x150Logo="assets\Square150x150Logo.png"
Square44x44Logo="assets\Square44x44Logo.png"
Description="Moodle Desktop: The official desktop app for Moodle.">
<uap:DefaultTile Wide310x150Logo="assets\Wide310x150Logo.png" />
</uap:VisualElements>
<Extensions>
<uap:Extension Category="windows.protocol">
<uap:Protocol Name="moodlemobile">
<uap:DisplayName>Moodle Mobile URI Scheme</uap:DisplayName>
</uap:Protocol>
</uap:Extension>
</Extensions>
</Application>
</Applications>
</Package>

View File

@ -1,265 +0,0 @@
// dialog isn't used, but not requiring it throws an error.
const {app, BrowserWindow, ipcMain, shell, dialog, Menu} = require('electron');
const path = require('path');
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.
let mainWindow,
appName = 'Moodle Desktop', // Default value.
isReady = false,
configRead = false;
function createWindow() {
// Create the browser window.
var width = 800,
height = 600;
const screen = require('electron').screen;
if (screen) {
const display = screen.getPrimaryDisplay();
if (display && display.workArea) {
width = display.workArea.width || width;
height = display.workArea.height || height;
}
}
const options = {
width: width,
height: height,
minWidth: 400,
minHeight: 400,
textAreasAreResizable: false,
plugins: true,
show: false // Don't show it until it's ready to prevent showing a blank screen.
};
if (os.platform().indexOf('linux') === 0) {
options.icon = path.join(__dirname, '/../www/assets/icon/icon.png');
}
mainWindow = new BrowserWindow(options);
// And load the index.html of the app.
mainWindow.loadURL(url.format({
pathname: path.join(__dirname, '/../www/index.html'),
protocol: 'file:',
slashes: true
}));
mainWindow.once('ready-to-show', () => {
mainWindow.show();
mainWindow.maximize();
});
// Emitted when the window is closed.
mainWindow.on('closed', () => {
// Dereference the window object.
mainWindow = null
});
mainWindow.on('focus', () => {
mainWindow.webContents.send('coreAppFocused'); // Send an event to the main window.
});
// 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.
// For some reason, gotTheLock is always false in signed Mac apps so we should ingore it.
// See https://github.com/electron/electron/issues/15958
var gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock && !isMac) {
// It's not the main instance of the app, kill it.
app.exit();
return;
}
app.on('second-instance', (event, commandLine, workingDirectory) => {
// Another instance was launched. If it was launched with a URL, it should be in the second param.
if (commandLine && commandLine[1]) {
appLaunched(commandLine[1]);
} else {
focusApp();
}
});
// This method will be called when Electron has finished initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', function() {
isReady = true;
createWindow();
if (configRead) {
setAppMenu();
}
});
// Quit when all windows are closed.
app.on('window-all-closed', () => {
app.exit();
});
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
createWindow();
}
});
// Read the config.json file.
fs.readFile(path.join(__dirname, 'config.json'), 'utf8', (err, data) => {
configRead = true;
// Default values.
var ssoScheme = 'moodlemobile',
appId = 'com.moodle.moodlemobile';
if (!err) {
try {
data = JSON.parse(data);
ssoScheme = data.customurlscheme;
appName = data.desktopappname;
appId = data.app_id;
} catch(ex) {}
}
// Set default protocol (custom URL scheme).
app.setAsDefaultProtocolClient(ssoScheme);
// Fix notifications in Windows.
app.setAppUserModelId(appId);
if (isReady) {
setAppMenu();
}
});
// Listen for open-url events (Mac OS only).
app.on('open-url', (event, url) => {
event.preventDefault();
appLaunched(url);
});
function appLaunched(url) {
// App was launched again with a URL. Focus the main window and send an event to treat the URL.
if (mainWindow) {
focusApp();
mainWindow.webContents.send('coreAppLaunched', url); // Send an event to the main window.
}
}
function focusApp() {
if (mainWindow) {
if (mainWindow.isMinimized()) {
mainWindow.restore();
}
mainWindow.focus();
}
}
// Listen for events sent by the renderer processes (windows).
ipcMain.on('openItem', (event, path) => {
var result;
// Add file:// protocol if it isn't there.
if (path.indexOf('file://') == -1) {
path = 'file://' + path;
}
if (os.platform().indexOf('darwin') > -1) {
// Use openExternal in MacOS because openItem doesn't work in sandboxed apps.
// https://github.com/electron/electron/issues/9005
result = shell.openExternal(path);
} else {
result = shell.openItem(path);
}
if (!result) {
// Cannot open file, probably no app to handle it. Open the folder.
result = shell.showItemInFolder(path.replace('file://', ''));
}
event.returnValue = result;
});
ipcMain.on('closeSecondaryWindows', () => {
const windows = BrowserWindow.getAllWindows();
for (let i = 0; i < windows.length; i++) {
const win = windows[i];
if (!win.isDestroyed() && (!mainWindow || win.id != mainWindow.id)) {
win.close();
}
}
});
ipcMain.on('focusApp', focusApp);
// Configure the app's menu.
function setAppMenu() {
let menuTemplate = [
{
label: appName,
role: 'window',
submenu: [
{
label: 'Quit',
accelerator: 'CmdorCtrl+Q',
role: 'close'
}
]
},
{
label: 'Edit',
submenu: [
{
label: 'Cut',
role: 'cut'
},
{
label: 'Copy',
role: 'copy'
},
{
label: 'Paste',
role: 'paste'
},
{
label: 'Select All',
role: 'selectall'
}
]
},
{
label: 'Help',
role: 'help',
submenu: [
{
label: 'Docs',
accelerator: 'CmdOrCtrl+H',
click() {
shell.openExternal('https://docs.moodle.org/en/Moodle_Mobile');
}
}
]
}
];
Menu.setApplicationMenu(Menu.buildFromTemplate(menuTemplate));
}

View File

@ -0,0 +1,47 @@
// (C) Copyright 2021 Moodle Pty Ltd.
//
// 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.
const { exec } = require('child_process');
const { resolve } = require('path');
/**
* Task to build a Moodle plugin to run Behat tests.
*/
class BuildBehatPluginTask {
/**
* Check whether Behat is configured to run app tests.
*
* @returns Whether Behat is configured.
*/
static isBehatConfigured() {
if (process.env.MOODLE_APP_BEHAT_PLUGIN_PATH) {
return !['0', 'false'].includes(process.env.MOODLE_APP_BEHAT_PLUGIN_PATH);
}
return !!process.env.MOODLE_DOCKER_WWWROOT;
}
/**
* Run the task.
*
* @param done Function to call when done.
*/
run(done) {
exec(resolve(__dirname, '../scripts/build-behat-plugin.js'), done);
}
}
module.exports = BuildBehatPluginTask;

View File

@ -1,138 +0,0 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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.
const gulp = require('gulp');
const through = require('through');
const bufferFrom = require('buffer-from');
const rename = require('gulp-rename');
const exec = require('child_process').exec;
const LICENSE = '' +
'// (C) Copyright 2015 Moodle Pty Ltd.\n' +
'//\n' +
'// Licensed under the Apache License, Version 2.0 (the "License");\n' +
'// you may not use this file except in compliance with the License.\n' +
'// You may obtain a copy of the License at\n' +
'//\n' +
'// http://www.apache.org/licenses/LICENSE-2.0\n' +
'//\n' +
'// Unless required by applicable law or agreed to in writing, software\n' +
'// distributed under the License is distributed on an "AS IS" BASIS,\n' +
'// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n' +
'// See the License for the specific language governing permissions and\n' +
'// limitations under the License.\n\n';
/**
* Task to convert config.json into a TypeScript class.
*/
class BuildConfigTask {
/**
* Run the task.
*
* @param path Path to the config file.
* @param done Function to call when done.
*/
run(path, done) {
const self = this;
// Get the last commit.
exec('git log -1 --pretty=format:"%H"', (err, commit, stderr) => {
if (err) {
console.error('An error occurred while getting the last commit: ' + err);
} else if (stderr) {
console.error('An error occurred while getting the last commit: ' + stderr);
}
gulp.src(path)
.pipe(through(function(file) {
// Convert the contents of the file into a TypeScript class.
// Disable the rule variable-name in the file.
const config = JSON.parse(file.contents.toString());
let contents = LICENSE + '// tslint:disable: variable-name\n' + 'export class CoreConfigConstants {\n';
for (let key in config) {
let value = self.transformValue(config[key]);
if (typeof config[key] != 'number' && typeof config[key] != 'boolean' && typeof config[key] != 'string') {
key = key + ': any';
}
// If key has quotation marks, remove them.
if (key[0] == '"') {
key = key.substr(1, key.length - 2);
}
contents += ' static ' + key + ' = ' + value + ';\n';
}
// Add compilation info.
contents += ' static compilationtime = ' + Date.now() + ';\n';
contents += ' static lastcommit = \'' + commit + '\';\n';
contents += '}\n';
file.contents = bufferFrom(contents);
this.emit('data', file);
}))
.pipe(rename('configconstants.ts'))
.pipe(gulp.dest('./src'))
.on('end', done);
});
}
/**
* Recursively transform a config value into personalized TS.
*
* @param value Value to convert
* @return Converted value.
*/
transformValue(value) {
if (typeof value == 'string') {
// Wrap the string in ' and escape them.
return "'" + value.replace(/([^\\])'/g, "$1\\'") + "'";
}
if (typeof value != 'number' && typeof value != 'boolean') {
const isArray = Array.isArray(value);
let contents = '';
let quoteKeys = false;
if (!isArray) {
for (let key in value) {
if (key.indexOf('-') >= 0) {
quoteKeys = true;
break;
}
}
}
for (let key in value) {
value[key] = this.transformValue(value[key]);
const quotedKey = quoteKeys ? "'" + key + "'" : key;
contents += ' ' + (isArray ? '' : quotedKey + ': ') + value[key] + ",\n";
}
contents += (isArray ? ']' : '}');
return (isArray ? '[' : '{') + "\n" + contents.replace(/^/gm, ' ');
}
return value;
}
}
module.exports = BuildConfigTask;

View File

@ -0,0 +1,76 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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.
const { execSync } = require('child_process');
const { existsSync, readFileSync, writeFile } = require('fs');
const { parse: parseJsonc } = require('jsonc-parser');
const { resolve } = require('path');
function getConfig(environment) {
const envSuffixesMap = {
testing: ['test', 'testing'],
development: ['dev', 'development'],
production: ['prod', 'production'],
};
const config = parseJsonc(readFileSync(resolve(__dirname, '../moodle.config.json')).toString());
const envSuffixes = (envSuffixesMap[environment] || []);
const envConfigPath = envSuffixes.map(suffix => resolve(__dirname, `../moodle.config.${suffix}.json`)).find(existsSync);
if (envConfigPath) {
const envConfig = parseJsonc(readFileSync(envConfigPath).toString());
for (const [key, value] of Object.entries(envConfig)) {
config[key] = value;
}
}
return config;
}
function getBuild(environment) {
const { version } = JSON.parse(readFileSync(resolve(__dirname, '../package.json')));
return {
version,
isProduction: environment === 'production',
isTesting: environment === 'testing',
isDevelopment: environment === 'development',
lastCommitHash: execSync('git log -1 --pretty=format:"%H"').toString(),
compilationTime: Date.now(),
};
}
/**
* Task to build an env file depending on the current environment.
*/
class BuildEnvTask {
/**
* Run the task.
*
* @param done Function to call when done.
*/
run(done) {
const envFile = resolve(__dirname, '../src/assets/env.json');
const env = {
config: getConfig(process.env.NODE_ENV || 'development'),
build: getBuild(process.env.NODE_ENV || 'development'),
};
writeFile(envFile, JSON.stringify(env), done);
}
}
module.exports = BuildEnvTask;

View File

@ -41,22 +41,24 @@ class BuildLangTask {
/**
* Run the task.
*
* @param language Language to treat.
* @param langPaths Paths to the possible language files.
* @param done Function to call when done.
*/
run(language, langPaths, done) {
const filename = language + '.json';
run(langPaths, done) {
const data = {};
let firstFile = null;
const self = this;
const paths = langPaths.map((path) => {
if (path.slice(-1) != '/') {
if (path.endsWith('.json')) {
return path;
}
if (!path.endsWith('/')) {
path = path + '/';
}
return path + language + '.json';
return path + 'lang.json';
});
gulp.src(paths, { allowEmpty: true })
@ -72,7 +74,7 @@ class BuildLangTask {
/* This implementation is based on gulp-jsoncombine module.
* https://github.com/reflog/gulp-jsoncombine */
if (firstFile) {
const joinedPath = pathLib.join(firstFile.base, language + '.json');
const joinedPath = pathLib.join(firstFile.base, 'en.json');
const joinedFile = new File({
cwd: firstFile.cwd,
@ -103,13 +105,24 @@ class BuildLangTask {
}
try {
let srcPos = file.path.lastIndexOf('/src/');
if (srcPos == -1) {
// It's probably a Windows environment.
srcPos = file.path.lastIndexOf('\\src\\');
}
let path = file.path;
let length = 9;
let srcPos = path.lastIndexOf('/src/app/');
if (srcPos < 0) {
// It's probably a Windows environment.
srcPos = path.lastIndexOf('\\src\\app\\');
}
if (srcPos < 0) {
length = 5;
srcPos = path.lastIndexOf('/src/');
if (srcPos < 0) {
// It's probably a Windows environment.
srcPos = path.lastIndexOf('\\src\\');
}
}
path = path.substr(srcPos + length);
const path = file.path.substr(srcPos + 5);
data[path] = JSON.parse(file.contents.toString());
} catch (err) {
console.log('Error parsing JSON: ' + err);
@ -125,42 +138,32 @@ class BuildLangTask {
treatMergedData(data) {
const merged = {};
const mergedOrdered = {};
const getPrefix = (path) => {
const folders = path.split(/[\/\\]/);
let filename = folders.pop();
switch (folders[0]) {
case 'core':
switch (folders[1]) {
case 'features':
return `core.${folders[2]}.`;
default:
return 'core.';
}
case 'addons':
return `addon.${folders.slice(1).join('_')}.`;
case 'assets':
filename = filename.split('.').slice(0, -1).join('.');
return `assets.${filename}.`;
default:
return `${folders[0]}.${folders[1]}.`;
}
}
for (let filepath in data) {
const pathSplit = filepath.split(/[\/\\]/);
let prefix;
pathSplit.pop();
switch (pathSplit[0]) {
case 'lang':
prefix = 'core';
break;
case 'core':
if (pathSplit[1] == 'lang') {
// Not used right now.
prefix = 'core';
} else {
prefix = 'core.' + pathSplit[1];
}
break;
case 'addon':
// Remove final item 'lang'.
pathSplit.pop();
// Remove first item 'addon'.
pathSplit.shift();
// For subplugins. We'll use plugin_subfolder_subfolder2_...
// E.g. 'mod_assign_feedback_comments'.
prefix = 'addon.' + pathSplit.join('_');
break;
case 'assets':
prefix = 'assets.' + pathSplit[1];
break;
}
const prefix = getPrefix(filepath);
if (prefix) {
this.addProperties(merged, data[filepath], prefix + '.');
this.addProperties(merged, data[filepath], prefix);
}
}

View File

@ -1,164 +0,0 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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.
const gulp = require('gulp');
const through = require('through');
const bufferFrom = require('buffer-from');
const concat = require('gulp-concat');
const pathLib = require('path');
const fs = require('fs');
/**
* Task to combine scss into a single file.
*/
class CombineScssTask {
/**
* Finds the file and returns its content.
*
* @param capture Import file path.
* @param baseDir Directory where the file was found.
* @param paths Alternative paths where to find the imports.
* @param parsedFiles Already parsed files to reduce size of the result.
* @return Partially combined scss.
*/
getReplace(capture, baseDir, paths, parsedFiles) {
let parse = pathLib.parse(pathLib.resolve(baseDir, capture + '.scss'));
let file = parse.dir + '/' + parse.name;
if (file.slice(-3) === '.wp') {
console.log('Windows Phone not supported "' + capture);
// File was already parsed, leave the import commented.
return '// @import "' + capture + '";';
}
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.
let x = 0;
while (!fs.existsSync(file + '.scss') && paths.length > x) {
parse = pathLib.parse(pathLib.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);
const text = fs.readFileSync(file);
// Recursive call.
return this.scssCombine(text, parse.dir, paths, parsedFiles);
}
/**
* Run the task.
*
* @param done Function to call when done.
*/
run(done) {
const paths = [
'node_modules/ionic-angular/themes/',
'node_modules/font-awesome/scss/',
'node_modules/ionicons/dist/scss/'
];
const parsedFiles = [];
const self = this;
gulp.src([
'./src/theme/variables.scss',
'./node_modules/ionic-angular/themes/ionic.globals.*.scss',
'./node_modules/ionic-angular/themes/ionic.components.scss',
'./src/**/*.scss',
]).pipe(through(function(file) { // Combine them based on @import and save it to stream.
if (file.isNull()) {
return;
}
parsedFiles.push(file);
file.contents = bufferFrom(self.scssCombine(
file.contents, pathLib.dirname(file.path), paths, parsedFiles));
this.emit('data', file);
})).pipe(concat('combined.scss')) // Concat the stream output in single file.
.pipe(gulp.dest('.')) // Save file to destination.
.on('end', done);
}
/**
* Combine scss files with its imports
*
* @param content Scss string to treat.
* @param baseDir Directory where the file was found.
* @param paths Alternative paths where to find the imports.
* @param parsedFiles Already parsed files to reduce size of the result.
* @return Scss string with the replaces done.
*/
scssCombine(content, baseDir, paths, parsedFiles) {
// Content is a Buffer, convert to string.
if (typeof content != "string") {
content = content.toString();
}
// Search of single imports.
let regex = /@import[ ]*['"](.*)['"][ ]*;/g;
if (regex.test(content)) {
return content.replace(regex, (m, capture) => {
if (capture == "bmma") {
return m;
}
return this.getReplace(capture, baseDir, paths, parsedFiles);
});
}
// Search of multiple imports.
regex = /@import(?:[ \n]+['"](.*)['"][,]?[ \n]*)+;/gm;
if (regex.test(content)) {
return content.replace(regex, (m, capture) => {
let text = '';
// Divide the import into multiple files.
const captures = m.match(/['"]([^'"]*)['"]/g);
for (let x in captures) {
text += this.getReplace(captures[x].replace(/['"]+/g, ''), baseDir, paths, parsedFiles) + '\n';
}
return text;
});
}
return content;
}
}
module.exports = CombineScssTask;

View File

@ -1,79 +0,0 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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.
const fs = require('fs');
const gulp = require('gulp');
const flatten = require('gulp-flatten');
const htmlmin = require('gulp-htmlmin');
const pathLib = require('path');
const TEMPLATES_SRC = [
'./src/components/**/*.html',
'./src/core/**/components/**/*.html',
'./src/core/**/component/**/*.html',
// Copy all addon components because any component can be injected using extraImports.
'./src/addon/**/components/**/*.html',
'./src/addon/**/component/**/*.html'
];
const TEMPLATES_DEST = './www/templates';
/**
* Task to copy component templates to www to make compile-html work in AOT.
*/
class CopyComponentTemplatesTask {
/**
* Delete a folder and all its contents.
*
* @param path [description]
* @return {[type]} [description]
*/
deleteFolderRecursive(path) {
if (fs.existsSync(path)) {
fs.readdirSync(path).forEach((file) => {
var curPath = pathLib.join(path, file);
if (fs.lstatSync(curPath).isDirectory()) {
this.deleteFolderRecursive(curPath);
} else {
fs.unlinkSync(curPath);
}
});
fs.rmdirSync(path);
}
}
/**
* Run the task.
*
* @param done Callback to call once done.
*/
run(done) {
this.deleteFolderRecursive(TEMPLATES_DEST);
gulp.src(TEMPLATES_SRC, { allowEmpty: true })
.pipe(flatten())
// Check options here: https://github.com/kangax/html-minifier
.pipe(htmlmin({
collapseWhitespace: true,
removeComments: true,
caseSensitive: true
}))
.pipe(gulp.dest(TEMPLATES_DEST))
.on('end', done);
}
}
module.exports = CopyComponentTemplatesTask;

View File

@ -12,57 +12,60 @@
// See the License for the specific language governing permissions and
// limitations under the License.
const BuildConfigTask = require('./gulp/task-build-config');
const BuildLangTask = require('./gulp/task-build-lang');
const CombineScssTask = require('./gulp/task-combine-scss');
const CopyComponentTemplatesTask = require('./gulp/task-copy-component-templates');
const BuildBehatPluginTask = require('./gulp/task-build-behat-plugin');
const BuildEnvTask = require('./gulp/task-build-env');
const PushTask = require('./gulp/task-push');
const Utils = require('./gulp/utils');
const gulp = require('gulp');
const pathLib = require('path');
const paths = {
lang: [
'./src/lang/',
'./src/core/**/lang/',
'./src/addon/**/lang/',
'./src/assets/countries/',
'./src/assets/mimetypes/'
'./src/addons/**/',
'./src/assets/countries.json',
'./src/assets/mimetypes.json',
'./src/core/features/**/',
'./src/core/',
],
config: './src/config.json',
};
const args = Utils.getCommandLineArguments();
// Build the language files into a single file per language.
gulp.task('lang', (done) => {
new BuildLangTask().run('en', paths.lang, done);
new BuildLangTask().run(paths.lang, done);
});
// Convert config.json into a TypeScript class.
gulp.task('config', (done) => {
new BuildConfigTask().run(paths.config, done);
// Build an env file depending on the current environment.
gulp.task('env', (done) => {
new BuildEnvTask().run(done);
});
// Copy component templates to www to make compile-html work in AOT.
gulp.task('copy-component-templates', (done) => {
new CopyComponentTemplatesTask().run(done);
});
// Combine SCSS files.
gulp.task('combine-scss', (done) => {
new CombineScssTask().run(done);
});
// Build a Moodle plugin to run Behat tests.
if (BuildBehatPluginTask.isBehatConfigured()) {
gulp.task('behat', (done) => {
new BuildBehatPluginTask().run(done);
});
}
gulp.task('push', (done) => {
new PushTask().run(args, done);
});
gulp.task('default', gulp.parallel('lang', 'config'));
gulp.task(
'default',
gulp.parallel([
'lang',
'env',
...(BuildBehatPluginTask.isBehatConfigured() ? ['behat'] : [])
]),
);
gulp.task('watch', () => {
const langsPaths = paths.lang.map(path => path + 'en.json');
gulp.watch(paths.lang, { interval: 500 }, gulp.parallel('lang'));
gulp.watch(['./moodle.config.json', './moodle.config.*.json'], { interval: 500 }, gulp.parallel('env'));
gulp.watch(langsPaths, { interval: 500 }, gulp.parallel('lang'));
gulp.watch(paths.config, { interval: 500 }, gulp.parallel('config'));
if (BuildBehatPluginTask.isBehatConfigured()) {
gulp.watch(['./tests/behat'], { interval: 500 }, gulp.parallel('behat'));
}
});

13
hooks/build 100755
View File

@ -0,0 +1,13 @@
#!/bin/bash
# Override DockerHub build hook in order to create images of different flavors (production & testing).
# See: https://docs.docker.com/docker-hub/builds/advanced/
if [[ "$IMAGE_NAME" == *-test ]]
then
docker build --build-arg build_command="npm run build:test" -f $DOCKERFILE_PATH -t $IMAGE_NAME .
elif [[ "$IMAGE_NAME" == *-dev ]]
then
docker build --build-arg build_command="npm run build" -f $DOCKERFILE_PATH -t $IMAGE_NAME .
else
docker build -f $DOCKERFILE_PATH -t $IMAGE_NAME .
fi

View File

@ -1,18 +0,0 @@
#!/bin/bash
set -e
if [ "integration" != "${SOURCE_BRANCH}" ]
then
# A space-separated list of additional tags to place on this image.
additionalTags=(latest)
# Tag and push image for each additional tag
for tag in ${additionalTags[@]}; do
echo "Tagging {$IMAGE_NAME} as ${DOCKER_REPO}:${tag}"
docker tag $IMAGE_NAME ${DOCKER_REPO}:${tag}
echo "Pushing ${DOCKER_REPO}:${tag}"
docker push ${DOCKER_REPO}:${tag}
done
fi

View File

@ -0,0 +1,43 @@
{
"rulesDirectory": ["@ionic/v4-migration-tslint/rules"],
"rules": {
"ion-action-sheet-method-create-parameters-renamed": true,
"ion-alert-method-create-parameters-renamed": true,
"ion-back-button-not-added-by-default": { "options": [true], "severity": "warning" },
"ion-button-attributes-renamed": true,
"ion-button-is-now-an-element": true,
"ion-buttons-attributes-renamed": true,
"ion-col-attributes-renamed": true,
"ion-datetime-capitalization-changed": true,
"ion-fab-attributes-renamed": true,
"ion-fab-button-is-now-an-element": true,
"ion-fab-fixed-content": true,
"ion-icon-attribute-is-active-removed": true,
"ion-item-attributes-renamed": true,
"ion-item-divider-ion-label-required": true,
"ion-item-ion-label-required": true,
"ion-item-is-now-an-element": true,
"ion-item-option-is-now-an-element": true,
"ion-item-option-method-get-sliding-percent-renamed": true,
"ion-item-options-attribute-values-renamed": true,
"ion-label-attributes-renamed": true,
"ion-list-header-ion-label-required": true,
"ion-loading-method-create-parameters-renamed": true,
"ion-menu-events-renamed": true,
"ion-menu-toggle-is-now-an-element": true,
"ion-navbar-is-now-ion-toolbar": true,
"ion-option-is-now-ion-select-option": true,
"ion-overlay-method-create-should-use-await": true,
"ion-overlay-method-present-should-use-await": { "options": [true], "severity": "warning" },
"ion-radio-attributes-renamed": true,
"ion-radio-group-is-now-an-element": true,
"ion-radio-slot-required": true,
"ion-range-attributes-renamed": true,
"ion-segment-button-ion-label-required": true,
"ion-spinner-attribute-values-renamed": true,
"ion-tabs-refactored": { "options": [true], "severity": "warning" },
"ion-text-is-now-an-element": true
}
}

View File

@ -1,10 +1,11 @@
{
"name": "moodlemobile",
"integrations": {
"cordova": {},
"gulp": {}
"cordova": {}
},
"type": "ionic-angular",
"watchPatterns": [],
"id": "com.moodle.moodlemobile"
"type": "angular",
"hooks": {
"build:before": "./scripts/copy-assets.js",
"serve:before": "./scripts/copy-assets.js"
}
}

22
jest.config.js 100644
View File

@ -0,0 +1,22 @@
const { pathsToModuleNameMapper } = require('ts-jest/utils');
const { compilerOptions } = require('./tsconfig');
module.exports = {
preset: 'jest-preset-angular',
setupFilesAfterEnv: ['<rootDir>/src/testing/setup.ts'],
testMatch: ['**/?(*.)test.ts'],
collectCoverageFrom: [
'src/**/*.{ts,html}',
'!src/testing/**/*',
],
transform: {
'^.+\\.(ts|html)$': 'ts-jest',
},
transformIgnorePatterns: ['node_modules/(?!@ionic-native|@ionic)'],
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: '<rootDir>/src/' }),
globals: {
'ts-jest': {
tsConfig: './tsconfig.test.json',
},
},
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,10 @@
/**
* Application config.
*
* You can create your own environment files such as "moodle.config.prod.json" and "moodle.config.dev.json"
* to override some values. The values will be merged, so you don't need to duplicate everything in this file.
*/
{
// You can find all the properties you can configure in src/types/config.d.ts
"default_lang": "es"
}

102
moodle.config.json 100644
View File

@ -0,0 +1,102 @@
{
"app_id": "com.moodle.moodlemobile",
"appname": "Moodle Mobile",
"versioncode": 3950,
"versionname": "3.9.5",
"cache_update_frequency_usually": 420000,
"cache_update_frequency_often": 1200000,
"cache_update_frequency_sometimes": 3600000,
"cache_update_frequency_rarely": 43200000,
"default_lang": "en",
"languages": {
"af": "Afrikaans",
"ar": "عربي",
"bg": "Български",
"ca": "Català",
"cs": "Čeština",
"da": "Dansk",
"de": "Deutsch",
"de-du": "Deutsch - Du",
"el": "Ελληνικά",
"en": "English",
"en-us": "English - United States",
"es": "Español",
"es-mx": "Español - México",
"eu": "Euskara",
"fa": "فارسی",
"fi": "Suomi",
"fr": "Français",
"gl": "Galego",
"he": "עברית",
"hi": "हिंदी",
"hr": "Hrvatski",
"hu": "magyar",
"hy": "Հայերեն",
"id": "Indonesian",
"it": "Italiano",
"ja": "日本語",
"km": "ខ្មែរ",
"kn": "ಕನ್ನಡ",
"ko": "한국어",
"lt": "Lietuvių",
"lv": "Latviešu",
"mn": "Монгол",
"mr": "मराठी",
"nl": "Nederlands",
"no": "Norsk - bokmål",
"pl": "Polski",
"pt": "Português - Portugal",
"pt-br": "Português - Brasil",
"ro": "Română",
"ru": "Русский",
"sl": "Slovenščina",
"sr-cr": "Српски",
"sr-lt": "Srpski",
"sv": "Svenska",
"tg": "Тоҷикӣ",
"tr": "Türkçe",
"uk": "Українська",
"uz": "O'zbekcha",
"vi": "Vietnamese",
"zh-cn": "简体中文",
"zh-tw": "正體中文"
},
"wsservice": "moodle_mobile_app",
"wsextservice": "local_mobile",
"demo_sites": {
"student": {
"url": "https:\/\/school.moodledemo.net",
"username": "student",
"password": "moodle"
},
"teacher": {
"url": "https:\/\/school.moodledemo.net",
"username": "teacher",
"password": "moodle"
}
},
"zoomlevels": {
"normal": 100,
"low": 110,
"high": 120
},
"customurlscheme": "moodlemobile",
"siteurl": "",
"sitename": "",
"multisitesdisplay": "",
"sitefindersettings": {},
"onlyallowlistedsites": false,
"skipssoconfirmation": false,
"forcedefaultlanguage": false,
"privacypolicy": "https:\/\/moodle.net\/moodle-app-privacy\/",
"notificoncolor": "#f98012",
"enableanalytics": false,
"enableonboarding": true,
"forceColorScheme": "",
"forceLoginLogo": false,
"ioswebviewscheme": "moodleappfs",
"appstores": {
"android": "com.moodle.moodlemobile",
"ios": "id633359593"
}
}

11
nginx.conf 100644
View File

@ -0,0 +1,11 @@
server {
listen 0.0.0.0:80;
root /usr/share/nginx/html;
server_tokens off;
access_log off;
location / {
try_files $uri $uri/ /index.html;
}
}

25669
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +1,11 @@
{
"name": "moodlemobile",
"version": "3.9.4",
"version": "3.9.5",
"description": "The official app for Moodle.",
"author": {
"name": "Moodle Pty Ltd.",
"email": "mobile@moodle.com"
},
"config": {
"ionic_webpack": "./config/webpack.config.js",
"ionic_copy": "./config/copy.config.js",
"ionic_uglifyjs": "./config/uglifyjs.config.js",
"ionic_sass": "./config/sass.config.js"
},
"repository": {
"type": "git",
"url": "https://github.com/moodlehq/moodlemobile2.git"
@ -24,88 +18,87 @@
}
],
"scripts": {
"start": "npm run dev",
"dev": "ionic serve",
"ng": "ng",
"start": "ionic serve",
"build": "ionic build",
"build:prod": "NODE_ENV=production ionic build --prod",
"build:test": "NODE_ENV=testing ionic build",
"dev:android": "ionic cordova run android --livereload",
"dev:ios": "ionic cordova run ios --livereload",
"prod:android": "ionic cordova run android --aot",
"prod:ios": "ionic cordova run ios --aot",
"setup": "npm install && npx cordova prepare && npx gulp",
"clean": "npx ionic-app-scripts clean",
"build": "npx ionic-app-scripts build",
"lint": "npx 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": "npx gulp",
"ionic:serve": "npx gulp watch & npx ionic-app-scripts serve -b --devapp --address=0.0.0.0",
"ionic:build:before": "npx gulp",
"ionic:watch:before": "npx gulp",
"ionic:build:after": "npx gulp copy-component-templates",
"preionic:build": "npx gulp",
"postionic:build": "npx gulp copy-component-templates",
"desktop.pack": "npx electron-builder --dir",
"desktop.dist": "npx electron-builder -p never",
"windows.store": "npx electron-windows-store --input-directory .\\desktop\\dist\\win-unpacked --output-directory .\\desktop\\store -a .\\resources\\desktop -m .\\desktop\\assets\\windows\\AppXManifest.xml --package-version 0.0.0.0 --package-name MoodleDesktop"
"prod:android": "NODE_ENV=production ionic cordova run android --aot",
"prod:ios": "NODE_ENV=production ionic cordova run ios --aot",
"test": "NODE_ENV=testing gulp && jest --verbose",
"test:ci": "NODE_ENV=testing gulp && jest -ci --runInBand --verbose",
"test:watch": "NODE_ENV=testing gulp watch & jest --watch",
"test:coverage": "NODE_ENV=testing gulp && jest --coverage",
"lint": "NODE_OPTIONS=--max-old-space-size=4096 ng lint",
"ionic:serve:before": "gulp",
"ionic:serve": "gulp watch & NODE_OPTIONS=--max-old-space-size=4096 ng serve",
"ionic:build:before": "gulp"
},
"dependencies": {
"@angular/animations": "5.2.11",
"@angular/common": "5.2.11",
"@angular/compiler": "5.2.11",
"@angular/compiler-cli": "5.2.11",
"@angular/core": "5.2.11",
"@angular/forms": "5.2.11",
"@angular/platform-browser": "5.2.11",
"@angular/platform-browser-dynamic": "5.2.11",
"@ionic-native/badge": "4.20.0",
"@ionic-native/camera": "4.20.0",
"@ionic-native/chooser": "4.20.0",
"@ionic-native/clipboard": "4.20.0",
"@ionic-native/core": "4.20.0",
"@ionic-native/device": "4.20.0",
"@ionic-native/diagnostic": "4.2.0",
"@ionic-native/file": "4.20.0",
"@ionic-native/file-opener": "4.20.0",
"@ionic-native/file-transfer": "4.20.0",
"@ionic-native/geolocation": "4.20.0",
"@ionic-native/globalization": "4.20.0",
"@ionic-native/http": "4.20.0",
"@ionic-native/in-app-browser": "4.20.0",
"@ionic-native/keyboard": "4.20.0",
"@ionic-native/local-notifications": "4.20.0",
"@ionic-native/media": "4.20.0",
"@ionic-native/media-capture": "4.20.0",
"@ionic-native/network": "4.20.0",
"@ionic-native/push": "4.20.0",
"@ionic-native/qr-scanner": "4.20.0",
"@ionic-native/screen-orientation": "4.20.0",
"@ionic-native/splash-screen": "4.20.0",
"@ionic-native/sqlite": "4.20.0",
"@ionic-native/status-bar": "4.20.0",
"@ionic-native/web-intent": "4.20.0",
"@ionic-native/zip": "4.20.0",
"@ngx-translate/core": "8.0.0",
"@ngx-translate/http-loader": "2.0.1",
"ajv": "6.11.0",
"chart.js": "2.9.3",
"@angular/animations": "10.0.14",
"@angular/common": "10.0.14",
"@angular/core": "10.0.14",
"@angular/forms": "10.0.14",
"@angular/platform-browser": "10.0.14",
"@angular/platform-browser-dynamic": "10.0.14",
"@angular/router": "10.0.14",
"@ionic-native/badge": "5.33.0",
"@ionic-native/camera": "5.33.0",
"@ionic-native/chooser": "5.33.0",
"@ionic-native/clipboard": "5.33.0",
"@ionic-native/core": "5.33.0",
"@ionic-native/device": "5.33.0",
"@ionic-native/diagnostic": "5.33.0",
"@ionic-native/file": "5.33.0",
"@ionic-native/file-opener": "5.33.0",
"@ionic-native/file-transfer": "5.33.0",
"@ionic-native/geolocation": "5.33.0",
"@ionic-native/http": "5.33.0",
"@ionic-native/in-app-browser": "5.33.0",
"@ionic-native/ionic-webview": "5.33.0",
"@ionic-native/keyboard": "5.33.0",
"@ionic-native/local-notifications": "5.33.0",
"@ionic-native/media": "5.33.0",
"@ionic-native/media-capture": "5.33.0",
"@ionic-native/network": "5.33.0",
"@ionic-native/push": "5.33.0",
"@ionic-native/qr-scanner": "5.33.0",
"@ionic-native/splash-screen": "5.33.0",
"@ionic-native/sqlite": "5.33.0",
"@ionic-native/status-bar": "5.33.0",
"@ionic-native/web-intent": "5.33.0",
"@ionic-native/zip": "5.33.0",
"@ionic/angular": "5.6.6",
"@ngx-translate/core": "13.0.0",
"@ngx-translate/http-loader": "6.0.0",
"@types/chart.js": "2.9.31",
"@types/cordova": "0.0.34",
"@types/cordova-plugin-file-transfer": "1.6.2",
"@types/dom-mediacapture-record": "1.0.7",
"chart.js": "2.9.4",
"com-darryncampbell-cordova-plugin-intent": "1.3.0",
"cordova": "10.0.0",
"cordova-android": "8.1.0",
"cordova-android": "9.1.0",
"cordova-android-support-gradle-release": "3.0.1",
"cordova-clipboard": "1.3.0",
"cordova-ios": "5.1.1",
"cordova-plugin-advanced-http": "2.4.1",
"cordova-ios": "6.2.0",
"cordova-plugin-add-swift-support": "2.0.2",
"cordova-plugin-advanced-http": "3.1.0",
"cordova-plugin-badge": "0.8.8",
"cordova-plugin-camera": "4.1.0",
"cordova-plugin-camera": "5.0.1",
"cordova-plugin-chooser": "1.3.2",
"cordova-plugin-customurlscheme": "5.0.1",
"cordova-plugin-customurlscheme": "5.0.2",
"cordova-plugin-device": "2.0.3",
"cordova-plugin-file": "6.0.2",
"cordova-plugin-file-opener2": "3.0.4",
"cordova-plugin-file-transfer": "1.7.1",
"cordova-plugin-geolocation": "git+https://github.com/apache/cordova-plugin-geolocation.git#89cf51d222e8f225bdfb661965b3007d669c40ff",
"cordova-plugin-file-opener2": "3.0.5",
"cordova-plugin-file-transfer": "git+https://github.com/moodlemobile/cordova-plugin-file-transfer.git",
"cordova-plugin-geolocation": "4.1.0",
"cordova-plugin-globalization": "1.11.0",
"cordova-plugin-inappbrowser": "git+https://github.com/moodlemobile/cordova-plugin-inappbrowser.git#moodle",
"cordova-plugin-ionic-keyboard": "2.1.3",
"cordova-plugin-ionic-webview": "git+https://github.com/moodlemobile/cordova-plugin-ionic-webview.git#500-moodle",
"cordova-plugin-inappbrowser": "git+https://github.com/moodlemobile/cordova-plugin-inappbrowser.git#moodle-ionic5",
"cordova-plugin-ionic-keyboard": "2.2.0",
"cordova-plugin-ionic-webview": "5.0.0",
"cordova-plugin-local-notification": "git+https://github.com/moodlemobile/cordova-plugin-local-notification.git#moodle",
"cordova-plugin-media": "5.0.3",
"cordova-plugin-media-capture": "3.0.3",
@ -118,61 +111,71 @@
"cordova-plugin-wkuserscript": "git+https://github.com/moodlemobile/cordova-plugin-wkuserscript.git",
"cordova-plugin-wkwebview-cookies": "git+https://github.com/moodlemobile/cordova-plugin-wkwebview-cookies.git",
"cordova-plugin-zip": "3.1.0",
"cordova-sqlite-storage": "4.0.0",
"cordova-sqlite-storage": "6.0.0",
"cordova-support-google-services": "1.3.2",
"cordova.plugins.diagnostic": "5.0.2",
"core-js": "3.9.1",
"es6-promise-plugin": "4.2.2",
"font-awesome": "4.7.0",
"inquirer": "^7.3.2",
"ionic-angular": "3.9.9",
"ionicons": "3.0.0",
"jszip": "3.1.5",
"jszip": "3.5.0",
"mathjax": "2.7.7",
"moment": "2.24.0",
"moment": "2.29.0",
"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-v3",
"promise.prototype.finally": "3.1.0",
"rxjs": "5.5.12",
"sw-toolbox": "3.6.0",
"rxjs": "6.5.5",
"ts-md5": "1.2.7",
"web-animations-js": "2.3.2",
"zone.js": "0.8.29"
"tslib": "2.0.1",
"zone.js": "0.10.3"
},
"devDependencies": {
"@ionic/app-scripts": "3.2.3",
"@ionic/cli": "^6.11.7",
"@types/cordova": "^0.0.34",
"@types/cordova-plugin-file-transfer": "^0.0.3",
"@types/cordova-plugin-globalization": "^0.0.3",
"@types/cordova-plugin-network-information": "^0.0.3",
"@types/node": "^8.10.59",
"@types/promise.prototype.finally": "^2.0.4",
"acorn": "^5.7.4",
"cordova.plugins.diagnostic": "^5.0.2",
"electron-builder-lib": "^20.23.1",
"electron-rebuild": "^1.10.0",
"@angular-devkit/architect": "0.1101.2",
"@angular-devkit/build-angular": "0.1000.8",
"@angular-eslint/builder": "4.2.0",
"@angular-eslint/eslint-plugin": "4.2.0",
"@angular-eslint/eslint-plugin-template": "4.2.0",
"@angular-eslint/schematics": "4.2.0",
"@angular-eslint/template-parser": "4.2.0",
"@angular/cli": "10.0.8",
"@angular/compiler": "10.0.14",
"@angular/compiler-cli": "10.0.14",
"@angular/language-service": "10.0.14",
"@ionic/angular-toolkit": "2.3.3",
"@ionic/cli": "6.14.1",
"@types/faker": "5.1.3",
"@types/node": "12.12.64",
"@types/resize-observer-browser": "0.1.5",
"@types/webpack-env": "1.16.0",
"@typescript-eslint/eslint-plugin": "4.22.0",
"@typescript-eslint/parser": "4.22.0",
"check-es-compat": "1.1.1",
"cordova-plugin-prevent-override": "git+https://github.com/moodlemobile/cordova-plugin-prevent-override.git",
"eslint": "7.25.0",
"eslint-config-prettier": "8.3.0",
"eslint-plugin-header": "3.1.1",
"eslint-plugin-import": "2.22.1",
"eslint-plugin-jest": "24.3.6",
"eslint-plugin-jsdoc": "32.3.3",
"eslint-plugin-prefer-arrow": "1.2.3",
"eslint-plugin-promise": "5.1.0",
"faker": "5.1.0",
"fs-extra": "9.1.0",
"gulp": "4.0.2",
"gulp-clip-empty-files": "^0.1.2",
"gulp-concat": "^2.6.1",
"gulp-flatten": "^0.4.0",
"gulp-htmlmin": "^5.0.1",
"gulp-rename": "^2.0.0",
"gulp-slash": "^1.1.3",
"lodash.template": "^4.5.0",
"minimist": "^1.2.5",
"native-run": "^1.0.0",
"node-loader": "^0.6.0",
"request": "^2.88.2",
"through": "^2.3.8",
"typescript": "~2.6.2",
"vinyl": "^2.2.0",
"webpack-merge": "^4.2.2"
"gulp-clip-empty-files": "0.1.2",
"gulp-concat": "2.6.1",
"gulp-flatten": "0.4.0",
"gulp-htmlmin": "5.0.1",
"gulp-rename": "2.0.0",
"gulp-slash": "1.1.3",
"jest": "26.5.2",
"jest-preset-angular": "8.3.1",
"jsonc-parser": "2.3.1",
"native-run": "^1.4.0",
"ts-jest": "26.4.1",
"ts-node": "8.3.0",
"typescript": "3.9.9"
},
"optionalDependencies": {
"keytar": "^6.0.1"
},
"browser": {
"electron": false
"engines": {
"node": ">=12.x"
},
"cordova": {
"platforms": [
@ -180,118 +183,64 @@
"ios"
],
"plugins": {
"com-darryncampbell-cordova-plugin-intent": {},
"cordova-android-support-gradle-release": {
"ANDROID_SUPPORT_VERSION": "27.1.0"
},
"cordova-plugin-advanced-http": {},
"cordova-clipboard": {},
"cordova-plugin-badge": {},
"cordova-plugin-camera": {},
"cordova-plugin-camera": {
"ANDROID_SUPPORT_V4_VERSION": "27.+"
},
"cordova-plugin-chooser": {},
"cordova-plugin-customurlscheme": {
"URL_SCHEME": "moodlemobile"
"URL_SCHEME": "moodlemobile",
"ANDROID_SCHEME": " ",
"ANDROID_HOST": " ",
"ANDROID_PATHPREFIX": "/"
},
"cordova-plugin-device": {},
"cordova-plugin-file": {},
"cordova-plugin-file-opener2": {},
"cordova-plugin-file-transfer": {},
"cordova-plugin-globalization": {},
"cordova-plugin-file-opener2": {
"ANDROID_SUPPORT_V4_VERSION": "27.+"
},
"cordova-plugin-geolocation": {
"GPS_REQUIRED": "false"
},
"cordova-plugin-inappbrowser": {},
"cordova-plugin-ionic-keyboard": {},
"cordova-plugin-local-notification": {},
"cordova-plugin-ionic-webview": {},
"cordova-plugin-local-notification": {
"ANDROID_SUPPORT_V4_VERSION": "26.+"
},
"cordova-plugin-media-capture": {},
"cordova-plugin-media": {
"KEEP_AVAUDIOSESSION_ALWAYS_ACTIVE": "NO"
},
"cordova-plugin-network-information": {},
"cordova-plugin-qrscanner": {},
"cordova-plugin-screen-orientation": {},
"cordova-plugin-splashscreen": {},
"cordova-plugin-statusbar": {},
"cordova-plugin-whitelist": {},
"cordova-plugin-wkuserscript": {},
"cordova-plugin-wkwebview-cookies": {},
"cordova-plugin-zip": {},
"cordova-sqlite-storage": {},
"nl.kingsquare.cordova.background-audio": {},
"phonegap-plugin-push": {
"ANDROID_SUPPORT_V13_VERSION": "27.+",
"FCM_VERSION": "17.5.+"
"FCM_VERSION": "17.0.+"
},
"cordova-plugin-geolocation": {
"GEOLOCATION_USAGE_DESCRIPTION": "We need your location so you can attach it as part of your submissions.",
"GPS_REQUIRED": "false"
"com-darryncampbell-cordova-plugin-intent": {},
"nl.kingsquare.cordova.background-audio": {},
"cordova-android-support-gradle-release": {
"ANDROID_SUPPORT_VERSION": "27.+"
},
"cordova-plugin-ionic-webview": {},
"cordova-plugin-advanced-http": {
"OKHTTP_VERSION": "3.10.0"
"cordova.plugins.diagnostic": {
"ANDROID_SUPPORT_VERSION": "28.+"
},
"cordova-plugin-wkwebview-cookies": {},
"cordova-plugin-qrscanner": {},
"cordova-plugin-chooser": {},
"cordova-plugin-wkuserscript": {},
"cordova-plugin-media": {
"KEEP_AVAUDIOSESSION_ALWAYS_ACTIVE": "NO"
},
"cordova.plugins.diagnostic": {}
"cordova-plugin-globalization": {},
"cordova-plugin-file-transfer": {},
"cordova-plugin-prevent-override": {}
}
},
"main": "desktop/electron.js",
"build": {
"appId": "com.moodle.moodledesktop",
"productName": "Moodle Desktop",
"files": [
"desktop/electron.js",
"www/**/*",
"!config",
"!desktop/assets",
"!desktop/dist",
"!node_modules",
"!**/e2e",
"!hooks",
"!platforms",
"!plugins",
"!resources",
"!src",
"!**/*.scss"
],
"directories": {
"output": "desktop/dist"
},
"protocols": [
{
"name": "Moodle Mobile URL",
"schemes": [
"moodlemobile"
],
"role": "Viewer"
}
],
"compression": "maximum",
"electronVersion": "8.0.2",
"mac": {
"category": "public.app-category.education",
"icon": "resources/desktop/icon.icns",
"target": "mas",
"bundleVersion": "3.9.4",
"extendInfo": {
"ElectronTeamID": "2NU57U5PAW",
"NSLocationWhenInUseUsageDescription": "We need your location so you can attach it as part of your submissions.",
"NSLocationAlwaysUsageDescription": "We need your location so you can attach it as part of your submissions.",
"NSCameraUsageDescription": "We need camera access to take pictures so you can attach them as part of your submissions.",
"NSMicrophoneUsageDescription": "We need microphone access to record sounds so you can attach them as part of your submissions.",
"NSPhotoLibraryUsageDescription": "We need photo library access to get pictures from there so you can attach them as part of your submissions."
}
},
"win": {
"target": "appx",
"icon": "resources/desktop/icon.ico"
},
"linux": {
"category": "Education",
"target": "AppImage"
},
"snap": {
"confinement": "classic"
},
"nsis": {
"deleteAppDataOnUninstall": true
}
},
"engines": {
"node": ">=11.x"
"optionalDependencies": {
"keytar": "7.2.0"
}
}

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">localhost</domain>
</domain-config>
</network-security-config>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 932 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

View File

@ -1,32 +0,0 @@
#!/bin/bash
source "scripts/functions.sh"
if [ ! -z $GIT_ORG_PRIVATE ] && [ ! -z $GIT_TOKEN ] ; then
print_title "Run scripts"
git clone --depth 1 https://$GIT_TOKEN@github.com/$GIT_ORG_PRIVATE/apps-scripts.git ../scripts
cp ../scripts/*.sh scripts/
if [ $TRAVIS_BUILD_STAGE_NAME == 'prepare' ] && [ -f scripts/prepare.sh ] ; then
print_title 'Prepare Build'
./scripts/prepare.sh
if [ $? -ne 0 ]; then
exit 1
fi
elif [ $TRAVIS_BUILD_STAGE_NAME != 'prepare' ] && [ -f scripts/platform.sh ]; then
print_title 'Platform Build'
./scripts/platform.sh
if [ $? -ne 0 ]; then
exit 1
fi
fi
else
print_title "AOT Compilation"
# Dynamic template loading without errors.
sed -ie $'s~throw new Error("No ResourceLoader.*~url = "templates/" + url;\\\nvar resolve;\\\nvar reject;\\\nvar promise = new Promise(function (res, rej) {\\\nresolve = res;\\\nreject = rej;\\\n});\\\nvar xhr = new XMLHttpRequest();\\\nxhr.open("GET", url, true);\\\nxhr.responseType = "text";\\\nxhr.onload = function () {\\\nvar response = xhr.response || xhr.responseText;\\\nvar status = xhr.status === 1223 ? 204 : xhr.status;\\\nif (status === 0) {\\\nstatus = response ? 200 : 0;\\\n}\\\nif (200 <= status \&\& status <= 300) {\\\nresolve(response);\\\n}\\\nelse {\\\nreject("Failed to load " + url);\\\n}\\\n};\\\nxhr.onerror = function () { reject("Failed to load " + url); };\\\nxhr.send();\\\nreturn promise;\\\n~g' node_modules/@angular/platform-browser-dynamic/esm5/platform-browser-dynamic.js
# Do not run JS optimizations to avoid problems with site plugins.
sed -ie "s/context\.isProd || hasArg('--optimizeJs')/false/g" node_modules/@ionic/app-scripts/dist/util/config.js
npm run ionic:build -- --prod
fi

View File

@ -0,0 +1,104 @@
#!/usr/bin/env node
// (C) Copyright 2021 Moodle Pty Ltd.
//
// 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.
const { existsSync, readFileSync, writeFileSync } = require('fs');
const { readdir } = require('fs').promises;
const { mkdirSync, copySync } = require('fs-extra');
const { resolve } = require('path');
async function main() {
const pluginPath = process.argv[2] || guessPluginPath() || fail('Folder argument missing!');
if (!existsSync(pluginPath)) {
mkdirSync(pluginPath);
}
// Copy plugin template.
const { version: appVersion } = require(projectPath('package.json'));
const templatePath = projectPath('scripts/templates/behat-plugin');
const replacements = {
appVersion,
pluginVersion: getMoodlePluginVersion(),
};
copySync(templatePath, pluginPath);
for await (const templateFilePath of getDirectoryFiles(templatePath)) {
const pluginFilePath = pluginPath + templateFilePath.substr(templatePath.length);
const fileContents = readFileSync(pluginFilePath).toString();
writeFileSync(pluginFilePath, replaceArguments(fileContents, replacements));
}
// Copy features.
copySync(projectPath('tests/behat'), `${pluginPath}/tests/behat`);
}
function fail(message) {
console.error(message);
process.exit(1);
}
function guessPluginPath() {
if (process.env.MOODLE_APP_BEHAT_PLUGIN_PATH) {
return process.env.MOODLE_APP_BEHAT_PLUGIN_PATH;
}
if (process.env.MOODLE_DOCKER_WWWROOT) {
return `${process.env.MOODLE_DOCKER_WWWROOT}/local/moodleappbehat`;
}
return null;
}
function projectPath(path) {
return resolve(__dirname, '../', path);
}
async function* getDirectoryFiles(dir) {
const files = await readdir(dir, { withFileTypes: true });
for (const file of files) {
const path = resolve(dir, file.name);
if (file.isDirectory()) {
yield* getDirectoryFiles(path);
} else {
yield path;
}
}
}
function replaceArguments(text, args) {
for (const [arg, value] of Object.entries(args)) {
const regexp = new RegExp(`\\{\\{\\s+${arg}\\s+\\}\\}`, 'gm');
text = text.replace(regexp, value);
}
return text;
}
function getMoodlePluginVersion() {
const now = new Date();
const padded = (number, length = 2) => number.toString().padStart(length, '0');
const year = padded(now.getFullYear(), 4);
const month = padded(now.getMonth() + 1);
const day = padded(now.getDate());
return `${year}${month}${day}00`;
}
main();

24
scripts/build.sh 100755
View File

@ -0,0 +1,24 @@
#!/bin/bash
source "scripts/functions.sh"
if [ -z $GIT_TOKEN ]; then
print_error "Env vars not correctly defined"
exit 1
fi
print_title "Run scripts"
# TODO Change branch name.
git clone --depth 1 --single-branch --branch ionic5 https://$GIT_TOKEN@github.com/moodlemobile/apps-scripts.git ../scripts
cp ../scripts/*.sh scripts/
if [ ! -f scripts/platform.sh ]; then
print_error "Platform file not found"
exit 1
fi
print_title 'Platform Build'
./scripts/platform.sh
if [ $? -ne 0 ]; then
exit 1
fi

View File

@ -0,0 +1,39 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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.
/**
* Script to copy some files to the www folder.
*/
const fse = require('fs-extra');
const path = require('path');
// Assets to copy.
const ASSETS = {
'/node_modules/mathjax/MathJax.js': '/lib/mathjax/MathJax.js',
'/node_modules/mathjax/extensions': '/lib/mathjax/extensions',
'/node_modules/mathjax/jax/element': '/lib/mathjax/jax/element',
'/node_modules/mathjax/jax/input': '/lib/mathjax/jax/input',
'/node_modules/mathjax/jax/output/SVG': '/lib/mathjax/jax/output/SVG',
'/node_modules/mathjax/jax/output/PreviewHTML': '/lib/mathjax/jax/output/PreviewHTML',
'/node_modules/mathjax/localization': '/lib/mathjax/localization',
'/src/core/features/h5p/assets': '/lib/h5p',
};
module.exports = function(ctx) {
const assetsPath = ctx.project.srcDir + '/assets';
for (const src in ASSETS) {
fse.copySync(ctx.project.dir + src, assetsPath + ASSETS[src], { overwrite: true });
}
};

View File

@ -1,5 +1,12 @@
#!/bin/bash
#
# Script to create langindex from available language packs.
# ./create_langindex.sh [findbetter]
# If findbetter is set it will try to find a better solution for every key.
#
source "functions.sh"
source "lang_functions.sh"
#Saves or updates a key on langindex_old.json
function save_key {
@ -48,7 +55,8 @@ function exists_in_mobile {
}
function do_match {
match=$1
match=${1/\{\{/\{}
match=${match/\}\}/\}}
filematch=""
coincidence=`grep "$match" $LANGPACKSFOLDER/en/*.php | wc -l`
@ -56,7 +64,7 @@ function do_match {
filematch=`grep "$match" $LANGPACKSFOLDER/en/*.php | cut -d'/' -f5 | cut -d'.' -f1`
exists_in_file $filematch $plainid
elif [ $coincidence -gt 0 ] && [ "$#" -gt 1 ]; then
print_message $2
print_message "$2"
tput setaf 6
grep "$match" $LANGPACKSFOLDER/en/*.php
fi
@ -67,18 +75,21 @@ function find_matches {
do_match "string\[\'$plainid\'\] = \'$value\'" "Found EXACT match for $key in the following paths"
if [ $coincidence -gt 0 ]; then
case=1
save_key $key "TBD"
return
fi
do_match " = \'$value\'" "Found some string VALUES for $key in the following paths"
if [ $coincidence -gt 0 ]; then
case=2
save_key $key "TBD"
return
fi
do_match "string\[\'$plainid\'\]" "Found some string KEYS for $key in the following paths, value $value"
if [ $coincidence -gt 0 ]; then
case=3
save_key $key "TBD"
return
fi
@ -297,17 +308,13 @@ function array_contains {
done
}
print_title 'Generating language from code...'
gulp lang
npx gulp lang
print_title 'Getting languages'
git clone https://git.in.moodle.com/moodle/moodle-langpacks.git $LANGPACKSFOLDER
pushd $LANGPACKSFOLDER
BRANCHES=($(git branch -r --format="%(refname:lstrip=3)" --sort="refname" | grep MOODLE_))
BRANCH=${BRANCHES[${#BRANCHES[@]}-1]}
git checkout $BRANCH
git pull
popd
get_language en
print_title 'Processing file'
#Create langindex.json if not exists.
@ -324,4 +331,4 @@ jq -S --indent 2 -s '.[0]' langindex.json > langindex_new.json
mv langindex_new.json langindex.json
rm langindex_old.json
print_ok 'All done!'
print_ok 'All done!'

View File

@ -1,7 +1,5 @@
#!/bin/bash
LANGPACKSFOLDER='../../moodle-langpacks'
function check_success_exit {
if [ $? -ne 0 ]; then
print_error "$1"

View File

@ -0,0 +1,48 @@
<?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/>.
/**
* Script for getting the PHP structure of a WS returns or params.
*
* The first parameter (required) is the path to the Moodle installation to use.
* The second parameter (required) is the name to the WS to convert.
* The third parameter (optional) is a number: 1 to convert the params structure,
* 0 to convert the returns structure. Defaults to 0.
*/
if (!isset($argv[1])) {
echo "ERROR: Please pass the Moodle path as the first parameter.\n";
die();
}
$moodlepath = $argv[1];
define('CLI_SCRIPT', true);
require($moodlepath . '/config.php');
require($CFG->dirroot . '/webservice/lib.php');
require_once('ws_to_ts_functions.php');
$structures = get_all_ws_structures();
foreach ($structures as $wsname => $structure) {
remove_default_closures($structure->parameters_desc);
print_ws_structure($wsname, $structure->parameters_desc, true);
remove_default_closures($structure->returns_desc);
print_ws_structure($wsname, $structure->returns_desc, false);
}

View File

@ -28,16 +28,17 @@ if (!isset($argv[1])) {
die();
}
if (!isset($argv[2])) {
echo "ERROR: Please pass the WS name as the second parameter.\n";
die();
}
define('CLI_SCRIPT', true);
define('CACHE_DISABLE_ALL', true);
define('SERIALIZED', true);
require_once('ws_to_ts_functions.php');
$versions = array('master', '38', '37', '36', '35', '34', '33', '32', '31');
$versions = array('master', '310', '39', '38', '37', '36', '35', '34', '33', '32', '31');
$moodlespath = $argv[1];
$wsname = $argv[2];
@ -58,7 +59,12 @@ $previousversion = null;
$libsloaded = false;
foreach ($versions as $version) {
$moodlepath = concatenate_paths($moodlespath, 'stable_' . $version, $pathseparator);
$moodlepath = concatenate_paths($moodlespath, 'stable_' . $version . '/moodle', $pathseparator);
if (!file_exists($moodlepath)) {
echo "Folder does not exist for version $version, skipping...\n";
continue;
}
if (!$libsloaded) {
$libsloaded = true;

View File

@ -28,12 +28,15 @@ if (!isset($argv[1])) {
die();
}
if (!isset($argv[2])) {
echo "ERROR: Please pass the WS name as the second parameter.\n";
die();
}
if (!defined('SERIALIZED')) {
define('SERIALIZED', false);
}
$moodlepath = $argv[1];
$wsname = $argv[2];
$useparams = !!(isset($argv[3]) && $argv[3]);
@ -52,4 +55,9 @@ if ($structure === false) {
}
remove_default_closures($structure);
echo serialize($structure);
if (SERIALIZED) {
echo serialize($structure);
} else {
print_ws_structure($wsname, $structure, $useparams);
}

View File

@ -1,62 +0,0 @@
<?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/>.
/**
* Script for converting a PHP WS structure to a TS type.
*
* The first parameter (required) is the path to the Moodle installation to use.
* The second parameter (required) is the name to the WS to convert.
* The third parameter (optional) is the name to put to the TS type. Defaults to "TypeName".
* The fourth parameter (optional) is a number: 1 to convert the params structure,
* 0 to convert the returns structure. Defaults to 0.
*/
if (!isset($argv[1])) {
echo "ERROR: Please pass the Moodle path as the first parameter.\n";
die();
}
if (!isset($argv[2])) {
echo "ERROR: Please pass the WS name as the second parameter.\n";
die();
}
$moodlepath = $argv[1];
$wsname = $argv[2];
$typename = isset($argv[3]) ? $argv[3] : 'TypeName';
$useparams = !!(isset($argv[4]) && $argv[4]);
define('CLI_SCRIPT', true);
require($moodlepath . '/config.php');
require($CFG->dirroot . '/webservice/lib.php');
require_once('ws_to_ts_functions.php');
$structure = get_ws_structure($wsname, $useparams);
if ($structure === false) {
echo "ERROR: The WS wasn't found in this Moodle installation.\n";
die();
}
if ($useparams) {
$description = "Params of WS $wsname.";
} else {
$description = "Result of WS $wsname.";
}
echo get_ts_doc(null, $description, '') . "export type $typename = " . convert_to_ts(null, $structure, $useparams) . ";\n";

View File

@ -358,9 +358,7 @@ function detect_lang($lang, $keys) {
return false;
}
function save_key($key, $value, $path) {
$filePath = $path . '/en.json';
function save_key($key, $value, $filePath) {
$file = file_get_contents($filePath);
$file = (array) json_decode($file);
$value = html_entity_decode($value);
@ -387,27 +385,31 @@ function override_component_lang_files($keys, $translations) {
}
switch($type) {
case 'core':
case 'addon':
switch($component) {
case 'moodle':
$path .= 'lang';
$path .= 'core/lang.json';
break;
default:
$path .= $type.'/'.str_replace('_', '/', $component).'/lang';
$path .= 'core/features/'.str_replace('_', '/', $component).'/lang.json';
break;
}
break;
case 'addon':
$path .= 'addons/'.str_replace('_', '/', $component).'/lang.json';
break;
case 'assets':
$path .= $type.'/'.$component;
$path .= $type.'/'.$component.'.json';
break;
default:
$path .= $type.'/lang';
$path .= $type.'/lang.json';
break;
}
if (is_file($path.'/en.json')) {
if (is_file($path)) {
save_key($plainid, $value, $path);
} else {
echo "Cannot override: $path not found.\n";
}
}
}

View File

@ -0,0 +1,142 @@
#!/bin/bash
#
# Functions to fetch languages.
#
LANGPACKSFOLDER='../../moodle-langpacks'
BUCKET='moodle-lang-prod'
MOODLEORG_URL='https://download.moodle.org/download.php/direct/langpack'
DEFAULT_LASTVERSION='4.0'
# Checks if AWS is available and configured.
function check_aws {
AWS_SERVICE=1
aws --version &> /dev/null
if [ $? -ne 0 ]; then
AWS_SERVICE=0
echo 'AWS not installed. Check https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html for more info.'
return
fi
# In order to login to AWS, use credentials file or AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY vars.
if [ ! -f ~/.aws/credentials ] && ([ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ]); then
AWS_SERVICE=0
lastversion=$DEFAULT_LASTVERSION
echo 'AWS Cannot authenticate. Use aws configure or set the proper env vars.'
return
fi
}
# Get last version of Moodle to fetch latest languages.
function get_last_version {
if [ ! -z "${lastversion}" ]; then
return
fi
check_aws
if [ $AWS_SERVICE -eq 0 ]; then
lastversion=$DEFAULT_LASTVERSION
echo "Using default version $lastversion"
return
fi
list=`aws s3 ls s3://$BUCKET/`
if [ $? -ne 0 ]; then
AWS_SERVICE=0
lastversion=$DEFAULT_LASTVERSION
echo "AWS Cannot authenticate. Using default version $lastversion"
return
fi
lastversion=''
for folder in $list; do
if [ $folder != 'PRE' ]; then
lastversion=${folder/\//}
fi
done
if [ ! -z "${lastversion}" ]; then
echo "Last version $lastversion detected"
return
fi
lastversion=$DEFAULT_LASTVERSION
}
# Create langfolder
function create_langfolder {
if [ ! -d $LANGPACKSFOLDER ]; then
mkdir $LANGPACKSFOLDER
fi
}
# Get all language list from AWS.
function get_all_languages_aws {
langsfiles=`aws s3 ls s3://$BUCKET/$lastversion/`
langs=""
for file in $langsfiles; do
if [[ "$file" == *.zip ]]; then
file=${file/\.zip/}
langs+="$file "
fi
done
}
# Get language list from the installed ones (will not discover new translations).
function get_installed_languages {
langs=`jq -r '.languages | keys[]' ../moodle.config.json`
}
# Entry function to get a language file.
function get_language {
lang=$1
lang=${lang/-/_}
get_last_version
create_langfolder
echo "Getting $lang language"
pushd $LANGPACKSFOLDER > /dev/null
curl -s $MOODLEORG_URL/$lastversion/$lang.zip --output $lang.zip > /dev/null
rm -R $lang > /dev/null 2>&1> /dev/null
unzip -o -u $lang.zip > /dev/null
# This is the AWS version to get the language but right now it's slower.
# aws s3 cp s3://$BUCKET/$lastversion/$lang.zip . > /dev/null
rm $lang.zip
popd > /dev/null
}
# Entry function to get all language files.
function get_languages {
get_last_version
if [ -d $LANGPACKSFOLDER ]; then
lastupdate=`date -r $LANGPACKSFOLDER +%s`
currenttime=`date +%s`
ellapsedtime=$((currenttime - lastupdate))
if [ $ellapsedtime -lt 3600 ]; then
echo 'Recently updated, skip update languages'
return
fi
else
create_langfolder
fi
if [ $AWS_SERVICE -eq 1 ]; then
get_all_languages_aws
else
echo "Fallback language list will only get current installation languages"
get_installed_languages
fi
for lang in $langs; do
get_language "$lang"
done
}

View File

@ -228,12 +228,6 @@
"addon.coursecompletion.requirement": "block_completionstatus",
"addon.coursecompletion.status": "moodle",
"addon.coursecompletion.viewcoursereport": "completion",
"addon.files.couldnotloadfiles": "local_moodlemobileapp",
"addon.files.emptyfilelist": "local_moodlemobileapp",
"addon.files.erroruploadnotworking": "local_moodlemobileapp",
"addon.files.files": "moodle",
"addon.files.privatefiles": "moodle",
"addon.files.sitefiles": "moodle",
"addon.messageoutput_airnotifier.processorsettingsdesc": "local_moodlemobileapp",
"addon.messages.acceptandaddcontact": "message",
"addon.messages.addcontact": "message",
@ -243,6 +237,7 @@
"addon.messages.blocknoncontacts": "message",
"addon.messages.blockuser": "message",
"addon.messages.blockuserconfirm": "message",
"addon.messages.cantblockuser": "message",
"addon.messages.contactableprivacy": "message",
"addon.messages.contactableprivacy_coursemember": "message",
"addon.messages.contactableprivacy_onlycontacts": "message",
@ -287,6 +282,7 @@
"addon.messages.noncontacts": "message",
"addon.messages.nousersfound": "local_moodlemobileapp",
"addon.messages.numparticipants": "message",
"addon.messages.pendingcontactrequests": "message",
"addon.messages.removecontact": "message",
"addon.messages.removecontactconfirm": "message",
"addon.messages.removefromfavourites": "message",
@ -307,6 +303,8 @@
"addon.messages.unblockuser": "message",
"addon.messages.unblockuserconfirm": "message",
"addon.messages.unmuteconversation": "message",
"addon.messages.unreadconversations": "message",
"addon.messages.unreadmessages": "message",
"addon.messages.useentertosend": "message",
"addon.messages.useentertosenddescdesktop": "local_moodlemobileapp",
"addon.messages.useentertosenddescmac": "local_moodlemobileapp",
@ -382,10 +380,15 @@
"addon.mod_assign.noteam_desc": "assign",
"addon.mod_assign.notgraded": "assign",
"addon.mod_assign.numberofdraftsubmissions": "assign",
"addon.mod_assign.numberofdraftsubmissionscountdescription": "local_moodlemobileapp",
"addon.mod_assign.numberofparticipants": "assign",
"addon.mod_assign.numberofparticipantscountdescription": "local_moodlemobileapp",
"addon.mod_assign.numberofsubmissionsneedgrading": "assign",
"addon.mod_assign.numberofsubmissionsneedgradingcountdescription": "local_moodlemobileapp",
"addon.mod_assign.numberofsubmittedassignments": "assign",
"addon.mod_assign.numberofsubmittedassignmentscountdescription": "local_moodlemobileapp",
"addon.mod_assign.numberofteams": "assign",
"addon.mod_assign.numberofteamscountdescription": "local_moodlemobileapp",
"addon.mod_assign.numwords": "moodle",
"addon.mod_assign.outof": "assign",
"addon.mod_assign.overdue": "assign",
@ -414,7 +417,6 @@
"addon.mod_assign.ungroupedusersoptional": "assign",
"addon.mod_assign.unlimitedattempts": "assign",
"addon.mod_assign.userswhoneedtosubmit": "assign",
"addon.mod_assign.userwithid": "local_moodlemobileapp",
"addon.mod_assign.viewsubmission": "assign",
"addon.mod_assign.warningsubmissiongrademodified": "local_moodlemobileapp",
"addon.mod_assign.warningsubmissionmodified": "local_moodlemobileapp",
@ -434,6 +436,7 @@
"addon.mod_book.toc": "book",
"addon.mod_chat.beep": "chat",
"addon.mod_chat.chatreport": "chat",
"addon.mod_chat.confirmloss": "local_moodlemobileapp",
"addon.mod_chat.currentusers": "chat",
"addon.mod_chat.enterchat": "chat",
"addon.mod_chat.entermessage": "chat",
@ -539,6 +542,7 @@
"addon.mod_feedback.captchaofflinewarning": "local_moodlemobileapp",
"addon.mod_feedback.complete_the_form": "feedback",
"addon.mod_feedback.completed_feedbacks": "feedback",
"addon.mod_feedback.completedfeedbackscountdescription": "local_moodlemobileapp",
"addon.mod_feedback.continue_the_form": "feedback",
"addon.mod_feedback.feedback_is_not_open": "feedback",
"addon.mod_feedback.feedback_submitted_offline": "local_moodlemobileapp",
@ -561,6 +565,7 @@
"addon.mod_feedback.preview": "moodle",
"addon.mod_feedback.previous_page": "feedback",
"addon.mod_feedback.questions": "feedback",
"addon.mod_feedback.questionscountdescription": "local_moodlemobileapp",
"addon.mod_feedback.response_nr": "feedback",
"addon.mod_feedback.responses": "feedback",
"addon.mod_feedback.save_entries": "feedback",
@ -600,6 +605,7 @@
"addon.mod_forum.errorgetforum": "local_moodlemobileapp",
"addon.mod_forum.errorgetgroups": "local_moodlemobileapp",
"addon.mod_forum.errorposttoallgroups": "local_moodlemobileapp",
"addon.mod_forum.favourites": "forum",
"addon.mod_forum.favouriteupdated": "forum",
"addon.mod_forum.forumnodiscussionsyet": "local_moodlemobileapp",
"addon.mod_forum.group": "local_moodlemobileapp",
@ -867,6 +873,7 @@
"addon.mod_quiz.summaryofattempts": "quiz",
"addon.mod_quiz.timeleft": "quiz",
"addon.mod_quiz.timetaken": "quiz",
"addon.mod_quiz.unit": "quiz",
"addon.mod_quiz.warningattemptfinished": "local_moodlemobileapp",
"addon.mod_quiz.warningdatadiscarded": "local_moodlemobileapp",
"addon.mod_quiz.warningdatadiscardedfromfinished": "local_moodlemobileapp",
@ -1010,6 +1017,10 @@
"addon.mod_workshop.switchphase30": "workshop",
"addon.mod_workshop.switchphase40": "workshop",
"addon.mod_workshop.switchphase50": "workshop",
"addon.mod_workshop.taskdone": "workshop",
"addon.mod_workshop.taskfail": "workshop",
"addon.mod_workshop.taskinfo": "workshop",
"addon.mod_workshop.tasktodo": "workshop",
"addon.mod_workshop.userplan": "workshop",
"addon.mod_workshop.userplancurrentphase": "workshop",
"addon.mod_workshop.warningassessmentmodified": "local_moodlemobileapp",
@ -1019,6 +1030,7 @@
"addon.mod_workshop.yourassessmentfor": "workshop",
"addon.mod_workshop.yourgrades": "workshop",
"addon.mod_workshop.yoursubmission": "workshop",
"addon.mod_workshop.yoursubmissionwithassessments": "workshop",
"addon.mod_workshop_assessment_accumulative.dimensioncommentfor": "workshopform_accumulative",
"addon.mod_workshop_assessment_accumulative.dimensiongradefor": "workshopform_accumulative",
"addon.mod_workshop_assessment_accumulative.dimensionnumber": "workshopform_accumulative",
@ -1041,7 +1053,6 @@
"addon.notes.personalnotes": "notes",
"addon.notes.publishstate": "notes",
"addon.notes.sitenotes": "notes",
"addon.notes.userwithid": "local_moodlemobileapp",
"addon.notes.warningnotenotsent": "local_moodlemobileapp",
"addon.notifications.errorgetnotifications": "local_moodlemobileapp",
"addon.notifications.markallread": "moodle",
@ -1049,6 +1060,15 @@
"addon.notifications.notifications": "local_moodlemobileapp",
"addon.notifications.playsound": "local_moodlemobileapp",
"addon.notifications.therearentnotificationsyet": "local_moodlemobileapp",
"addon.notifications.unreadnotification": "message",
"addon.privatefiles.couldnotloadfiles": "local_moodlemobileapp",
"addon.privatefiles.emptyfilelist": "local_moodlemobileapp",
"addon.privatefiles.erroruploadnotworking": "local_moodlemobileapp",
"addon.privatefiles.files": "moodle",
"addon.privatefiles.privatefiles": "moodle",
"addon.privatefiles.sitefiles": "moodle",
"addon.qtype_essay.maxwordlimitboundary": "qtype_essay",
"addon.qtype_essay.minwordlimitboundary": "qtype_essay",
"addon.storagemanager.deletecourse": "local_moodlemobileapp",
"addon.storagemanager.deletecourses": "local_moodlemobileapp",
"addon.storagemanager.deletedatafrom": "local_moodlemobileapp",
@ -1388,6 +1408,7 @@
"core.clicktohideshow": "moodle",
"core.clicktoseefull": "local_moodlemobileapp",
"core.close": "repository",
"core.collapse": "moodle",
"core.comments": "moodle",
"core.comments.addcomment": "moodle",
"core.comments.comments": "moodle",
@ -1434,9 +1455,22 @@
"core.course.activitynotyetviewableremoteaddon": "local_moodlemobileapp",
"core.course.activitynotyetviewablesiteupgradeneeded": "local_moodlemobileapp",
"core.course.allsections": "local_moodlemobileapp",
"core.course.aria:sectionprogress": "local_moodlemobileapp",
"core.course.askadmintosupport": "local_moodlemobileapp",
"core.course.availablespace": "local_moodlemobileapp",
"core.course.cannotdeletewhiledownloading": "local_moodlemobileapp",
"core.course.completion_automatic:done": "course",
"core.course.completion_automatic:failed": "course",
"core.course.completion_automatic:todo": "course",
"core.course.completion_manual:aria:done": "course",
"core.course.completion_manual:aria:markdone": "course",
"core.course.completion_manual:done": "course",
"core.course.completion_manual:markdone": "course",
"core.course.completion_setby:auto:done": "course",
"core.course.completion_setby:auto:todo": "course",
"core.course.completion_setby:manual:done": "course",
"core.course.completion_setby:manual:markdone": "course",
"core.course.completionrequirements": "course",
"core.course.confirmdeletemodulefiles": "local_moodlemobileapp",
"core.course.confirmdeletestoreddata": "local_moodlemobileapp",
"core.course.confirmdownload": "local_moodlemobileapp",
@ -1449,6 +1483,8 @@
"core.course.couldnotloadsections": "local_moodlemobileapp",
"core.course.coursesummary": "moodle",
"core.course.downloadcourse": "tool_mobile",
"core.course.downloadcoursesprogressdescription": "local_moodlemobileapp",
"core.course.downloadsectionprogressdescription": "local_moodlemobileapp",
"core.course.errordownloadingcourse": "local_moodlemobileapp",
"core.course.errordownloadingsection": "local_moodlemobileapp",
"core.course.errorgetmodule": "local_moodlemobileapp",
@ -1460,6 +1496,7 @@
"core.course.nocontentavailable": "local_moodlemobileapp",
"core.course.overriddennotice": "grades",
"core.course.refreshcourse": "local_moodlemobileapp",
"core.course.section": "moodle",
"core.course.sections": "moodle",
"core.course.useactivityonbrowser": "local_moodlemobileapp",
"core.course.warningmanualcompletionmodified": "local_moodlemobileapp",
@ -1468,6 +1505,10 @@
"core.coursenogroups": "local_moodlemobileapp",
"core.courses.addtofavourites": "block_myoverview",
"core.courses.allowguests": "enrol_guest",
"core.courses.aria:coursecategory": "course",
"core.courses.aria:coursename": "course",
"core.courses.aria:courseprogress": "block_myoverview",
"core.courses.aria:favourite": "course",
"core.courses.availablecourses": "moodle",
"core.courses.cannotretrievemorecategories": "local_moodlemobileapp",
"core.courses.categories": "moodle",
@ -1480,6 +1521,7 @@
"core.courses.errorloadplugins": "local_moodlemobileapp",
"core.courses.errorsearching": "local_moodlemobileapp",
"core.courses.errorselfenrol": "local_moodlemobileapp",
"core.courses.favourite": "course",
"core.courses.filtermycourses": "local_moodlemobileapp",
"core.courses.frontpage": "admin",
"core.courses.hidecourse": "block_myoverview",
@ -1502,6 +1544,7 @@
"core.courses.selfenrolment": "local_moodlemobileapp",
"core.courses.sendpaymentbutton": "enrol_paypal",
"core.courses.show": "block_myoverview",
"core.courses.therearecourses": "moodle",
"core.courses.totalcoursesearchresults": "local_moodlemobileapp",
"core.currentdevice": "local_moodlemobileapp",
"core.datastoredoffline": "local_moodlemobileapp",
@ -1559,6 +1602,7 @@
"core.errorinvalidresponse": "local_moodlemobileapp",
"core.errorloadingcontent": "local_moodlemobileapp",
"core.errorofflinedisabled": "local_moodlemobileapp",
"core.erroropenfiledownloading": "local_moodlemobileapp",
"core.erroropenfilenoapp": "local_moodlemobileapp",
"core.erroropenfilenoextension": "local_moodlemobileapp",
"core.erroropenpopup": "local_moodlemobileapp",
@ -1568,6 +1612,7 @@
"core.errorsyncblocked": "local_moodlemobileapp",
"core.errorurlschemeinvalidscheme": "local_moodlemobileapp",
"core.errorurlschemeinvalidsite": "local_moodlemobileapp",
"core.expand": "moodle",
"core.explanationdigitalminor": "moodle",
"core.favourites": "moodle",
"core.filename": "repository",
@ -1605,16 +1650,23 @@
"core.forcepasswordchangenotice": "moodle",
"core.fulllistofcourses": "moodle",
"core.fullnameandsitename": "local_moodlemobileapp",
"core.grades.aggregatemean": "grades",
"core.grades.aggregatesum": "grades",
"core.grades.average": "grades",
"core.grades.badgrade": "grades",
"core.grades.calculatedgrade": "grades",
"core.grades.category": "grades",
"core.grades.contributiontocoursetotal": "grades",
"core.grades.feedback": "grades",
"core.grades.grade": "grades",
"core.grades.gradeitem": "grades",
"core.grades.gradepass": "grades",
"core.grades.grades": "grades",
"core.grades.lettergrade": "grades",
"core.grades.manualitem": "grades",
"core.grades.nogradesreturned": "grades",
"core.grades.nooutcome": "grades",
"core.grades.outcome": "grades",
"core.grades.percentage": "grades",
"core.grades.range": "grades",
"core.grades.rank": "grades",
@ -1716,13 +1768,16 @@
"core.hasdatatosync": "local_moodlemobileapp",
"core.help": "moodle",
"core.hide": "moodle",
"core.hideadvanced": "form",
"core.hour": "moodle",
"core.hours": "moodle",
"core.humanreadablesize": "local_moodlemobileapp",
"core.iframehelp": "local_moodlemobileapp",
"core.image": "local_moodlemobileapp",
"core.imageviewer": "local_moodlemobileapp",
"core.info": "moodle",
"core.invalidformdata": "error",
"core.ioscookieshelp": "local_moodlemobileapp",
"core.labelsep": "langconfig",
"core.lastaccess": "moodle",
"core.lastdownloaded": "local_moodlemobileapp",
@ -1840,6 +1895,7 @@
"core.login.signuprequiredfieldnotsupported": "local_moodlemobileapp",
"core.login.siteaddress": "local_moodlemobileapp",
"core.login.siteaddressplaceholder": "donottranslate",
"core.login.sitebadgedescription": "local_moodlemobileapp",
"core.login.sitehasredirect": "local_moodlemobileapp",
"core.login.siteinmaintenance": "local_moodlemobileapp",
"core.login.sitepolicynotagreederror": "local_moodlemobileapp",
@ -1859,6 +1915,7 @@
"core.lostconnection": "local_moodlemobileapp",
"core.mainmenu.changesite": "local_moodlemobileapp",
"core.mainmenu.help": "moodle",
"core.mainmenu.home": "moodle",
"core.mainmenu.logout": "moodle",
"core.mainmenu.website": "local_moodlemobileapp",
"core.maxfilesize": "moodle",
@ -1928,6 +1985,9 @@
"core.openfullimage": "local_moodlemobileapp",
"core.openinbrowser": "local_moodlemobileapp",
"core.openmodinbrowser": "local_moodlemobileapp",
"core.opensecurityquestion": "local_moodlemobileapp",
"core.opensettings": "local_moodlemobileapp",
"core.openwith": "local_moodlemobileapp",
"core.othergroups": "group",
"core.pagea": "moodle",
"core.parentlanguage": "langconfig",
@ -1935,6 +1995,7 @@
"core.percentagenumber": "local_moodlemobileapp",
"core.phone": "moodle",
"core.pictureof": "moodle",
"core.play": "local_moodlemobileapp",
"core.previous": "moodle",
"core.proceed": "moodle",
"core.pulltorefresh": "local_moodlemobileapp",
@ -1982,6 +2043,8 @@
"core.save": "moodle",
"core.savechanges": "assign",
"core.scanqr": "local_moodlemobileapp",
"core.scrollbackward": "local_moodlemobileapp",
"core.scrollforward": "local_moodlemobileapp",
"core.search": "moodle",
"core.searching": "local_moodlemobileapp",
"core.searchresults": "moodle",
@ -2001,9 +2064,10 @@
"core.settings.cannotsyncoffline": "local_moodlemobileapp",
"core.settings.cannotsyncwithoutwifi": "local_moodlemobileapp",
"core.settings.colorscheme": "local_moodlemobileapp",
"core.settings.colorscheme-auto": "local_moodlemobileapp",
"core.settings.colorscheme-dark": "local_moodlemobileapp",
"core.settings.colorscheme-light": "local_moodlemobileapp",
"core.settings.colorscheme-system": "local_moodlemobileapp",
"core.settings.colorscheme-system-notice": "local_moodlemobileapp",
"core.settings.compilationinfo": "local_moodlemobileapp",
"core.settings.copyinfo": "local_moodlemobileapp",
"core.settings.cordovadevicemodel": "local_moodlemobileapp",
@ -2036,6 +2100,9 @@
"core.settings.fontsizecharacter": "block_accessibility/char",
"core.settings.forcedsetting": "local_moodlemobileapp",
"core.settings.general": "moodle",
"core.settings.helpusimprove": "local_moodlemobileapp",
"core.settings.ioscookies": "local_moodlemobileapp",
"core.settings.ioscookiesdescription": "local_moodlemobileapp",
"core.settings.language": "moodle",
"core.settings.license": "moodle",
"core.settings.localnotifavailable": "local_moodlemobileapp",
@ -2075,6 +2142,7 @@
"core.sharedfiles.sharedfiles": "local_moodlemobileapp",
"core.sharedfiles.successstorefile": "local_moodlemobileapp",
"core.show": "moodle",
"core.showadvanced": "form",
"core.showless": "form",
"core.showmore": "form",
"core.site": "moodle",
@ -2123,6 +2191,7 @@
"core.tag.tagarea_course_modules": "tag",
"core.tag.tagarea_post": "tag",
"core.tag.tagarea_user": "tag",
"core.tag.tagareabadgedescription": "local_moodlemobileapp",
"core.tag.tags": "moodle",
"core.tag.warningareasnotsupported": "local_moodlemobileapp",
"core.teachers": "moodle",
@ -2131,6 +2200,7 @@
"core.time": "moodle",
"core.timesup": "quiz",
"core.today": "moodle",
"core.toggledelete": "local_moodlemobileapp",
"core.tryagain": "local_moodlemobileapp",
"core.twoparagraphs": "local_moodlemobileapp",
"core.uhoh": "local_moodlemobileapp",
@ -2168,6 +2238,7 @@
"core.user.sendemail": "local_moodlemobileapp",
"core.user.student": "moodle/defaultcoursestudent",
"core.user.teacher": "moodle/noneditingteacher",
"core.user.userwithid": "local_moodlemobileapp",
"core.user.webpage": "moodle",
"core.userdeleted": "moodle",
"core.userdetails": "moodle",

View File

@ -1,80 +0,0 @@
#!/bin/bash
source "scripts/functions.sh"
npm run build --bailOnLintError true --typeCheckOnLint true
if [ $? -ne 0 ]; then
exit 1
fi
if [ -z $GIT_ORG_PRIVATE ] || [ -z $GIT_TOKEN ]; then
print_error "Env vars not correctly defined"
exit 1
fi
# List first level of installed libraries so we can check the installed versions.
print_title "NPM packages list"
npm list --depth=0
if [ "$TRAVIS_BRANCH" == 'master' ]; then
print_title "Update langpacks"
cd scripts
./update_lang.sh
cd ..
print_title "Update generated lang files"
git remote set-url origin https://$GIT_TOKEN@github.com/$TRAVIS_REPO_SLUG.git
git fetch -q origin
git add -A src/assets/lang
git add */en.json
git add src/config.json
git commit -m 'Update lang files [ci skip]'
print_title "Update Licenses"
npm install -g license-checker
jq --version
license-checker --json --production --relativeLicensePath > licenses.json
jq 'del(.[].path)' licenses.json > licenses_old.json
mv licenses_old.json licenses.json
licenses=`jq -r 'keys[]' licenses.json`
echo "{" > licensesurl.json
first=1
for license in $licenses; do
obj=`jq --arg lic $license '.[$lic]' licenses.json`
licensePath=`echo $obj | jq -r '.licenseFile'`
file=""
if [[ ! -z "$licensePath" ]] || [[ "$licensePath" != "null" ]]; then
file=$(basename $licensePath)
if [ $first -eq 1 ] ; then
first=0
echo "\"$license\" : { \"licenseFile\" : \"$file\"}" >> licensesurl.json
else
echo ",\"$license\" : { \"licenseFile\" : \"$file\"}" >> licensesurl.json
fi
fi
done
echo "}" >> licensesurl.json
jq -s '.[0] * .[1]' licenses.json licensesurl.json > licenses_old.json
mv licenses_old.json licenses.json
rm licensesurl.json
git add licenses.json
git commit -m 'Update licenses [ci skip]'
git push origin HEAD:$TRAVIS_BRANCH
notify_on_error_exit "MIRROR: Unsuccessful push, stopping..."
fi
if [ "$TRAVIS_BRANCH" == 'integration' ] || [ "$TRAVIS_BRANCH" == 'master' ] || [ "$TRAVIS_BRANCH" == 'desktop' ] ; then
print_title "Mirror repository"
git remote add mirror https://$GIT_TOKEN@github.com/$GIT_ORG_PRIVATE/moodleapp.git
git fetch -q --unshallow mirror
notify_on_error_exit "MIRROR: Unsuccessful fetch of mirror, stopping..."
git fetch -q origin --depth=100
notify_on_error_exit "MIRROR: Unsuccessful fetch of origin, stopping..."
git push -f mirror HEAD:$TRAVIS_BRANCH
notify_on_error_exit "MIRROR: Unsuccessful mirror, stopping..."
git push -f mirror --tags
notify_on_error_exit "MIRROR: Unsuccessful mirror tags, stopping..."
fi

View File

@ -25,7 +25,7 @@ if (isset($_SERVER['REMOTE_ADDR'])) {
define('MOODLE_INTERNAL', 1);
define('LANGPACKSFOLDER', '../../moodle-langpacks');
define('ASSETSPATH', '../src/assets/lang/');
define('CONFIG', '../src/config.json');
define('CONFIG', '../moodle.config.json');
define('OVERRIDE_LANG_SUFIX', false);
global $strings;
@ -46,6 +46,11 @@ if (isset($argv[1]) && !empty($argv[1])) {
$languages = $config_langs;
}
if (!file_exists(ASSETSPATH)) {
mkdir(ASSETSPATH);
}
$keys = get_langindex_keys();
$added_langs = build_languages($languages, $keys);

View File

@ -0,0 +1,3 @@
<?php
$string['pluginname'] = 'Moodle App Behat (auto-generated)';

View File

@ -0,0 +1,13 @@
<?php
/**
* This plugin has been auto-generated, please don't modify it.
*/
defined('MOODLE_INTERNAL') || die;
$plugin->version = {{ pluginVersion }};
$plugin->requires = 2016052300;
$plugin->maturity = MATURITY_STABLE;
$plugin->release = '{{ appVersion }}';
$plugin->component = 'local_moodleappbehat';

View File

@ -10,21 +10,22 @@ dockercompose="$dockerscripts/moodle-docker-compose"
export MOODLE_DOCKER_DB=pgsql
export MOODLE_DOCKER_BROWSER=chrome
export MOODLE_DOCKER_WWWROOT="$HOME/moodle"
export MOODLE_DOCKER_PHP_VERSION=7.3
export MOODLE_DOCKER_PHP_VERSION=7.4
export MOODLE_DOCKER_APP_PATH=$basedir
# Prepare dependencies
print_title "Preparing dependencies"
git clone --branch master --depth 1 git://github.com/moodle/moodle $HOME/moodle
git clone --branch master --depth 1 git://github.com/moodlehq/moodle-local_moodlemobileapp $HOME/moodle/local/moodlemobileapp
git clone --branch master --depth 1 git://github.com/moodlehq/moodle-docker $HOME/moodle-docker
git clone --branch ionic5 --depth 1 git://github.com/moodlehq/moodle-local_moodlemobileapp $HOME/moodle/local/moodlemobileapp
# TODO replace for moodlehq/moodle-docker after merging https://github.com/moodlehq/moodle-docker/pull/156
git clone --branch MOBILE-3738 --depth 1 git://github.com/NoelDeMartin/moodle-docker $HOME/moodle-docker
cp $HOME/moodle-docker/config.docker-template.php $HOME/moodle/config.php
# Build app
print_title "Building app"
npm install
npm run setup
npm ci
# Start containers
print_title "Starting containers"

View File

@ -1,23 +1,23 @@
#!/bin/bash
#
# Script to update language packs on assets and detect new translated languages.
# ./update_lang.sh [language]
# If language is set it will only update the selected language.
#
source "functions.sh"
forceLang=$1
source "lang_functions.sh"
print_title 'Getting languages'
git clone --depth 1 --no-single-branch https://git.in.moodle.com/moodle/moodle-langpacks.git $LANGPACKSFOLDER
pushd $LANGPACKSFOLDER
BRANCHES=($(git branch -r --format="%(refname:lstrip=3)" --sort="refname" | grep MOODLE_))
BRANCH=${BRANCHES[${#BRANCHES[@]}-1]}
git checkout $BRANCH
git pull
popd
forceLang=$1
print_title 'Getting local mobile langs'
git clone --depth 1 https://github.com/moodlehq/moodle-local_moodlemobileapp.git ../../moodle-local_moodlemobileapp
if [ -z $forceLang ]; then
get_languages
php -f moodle_to_json.php
else
get_language "$forceLang"
php -f moodle_to_json.php "$forceLang"
fi
print_ok 'All done!'
print_ok 'All done!'

View File

@ -25,19 +25,34 @@ function get_ws_structure($wsname, $useparams) {
global $DB;
// get all the function descriptions
$functions = $DB->get_records('external_functions', array(), 'name');
$function = $DB->get_record('external_functions', array('services' => 'moodle_mobile_app', 'name' => $wsname));
if (!$function) {
return false;
}
$functiondesc = external_api::external_function_info($function);
if ($useparams) {
return $functiondesc->parameters_desc;
} else {
return $functiondesc->returns_desc;
}
}
/**
* Return all WS structures.
*/
function get_all_ws_structures() {
global $DB;
// get all the function descriptions
$functions = $DB->get_records('external_functions', array('services' => 'moodle_mobile_app'), 'name');
$functiondescs = array();
foreach ($functions as $function) {
$functiondescs[$function->name] = external_api::external_function_info($function);
}
if (!isset($functiondescs[$wsname])) {
return false;
} else if ($useparams) {
return $functiondescs[$wsname]->parameters_desc;
} else {
return $functiondescs[$wsname]->returns_desc;
}
return $functiondescs;
}
/**
@ -51,6 +66,16 @@ function fix_comment($desc) {
$desc .= '.';
}
$lines = explode("\n", $desc);
if (count($lines) > 1) {
$desc = array_shift($lines)."\n";
foreach ($lines as $i => $line) {
$spaces = strlen($line) - strlen(ltrim($line));
$desc .= str_repeat(' ', $spaces - 3) . '// '. ltrim($line)."\n";
}
}
return $desc;
}
@ -86,7 +111,7 @@ function get_ts_doc($type, $desc, $indentation) {
function convert_key_type($key, $type, $required, $indentation) {
if ($key) {
// It has a key, it's inside an object.
return $indentation . "$key" . ($required == VALUE_OPTIONAL ? '?' : '') . ": $type";
return $indentation . "$key" . ($required == VALUE_OPTIONAL || $required == VALUE_DEFAULT ? '?' : '') . ": $type";
} else {
// No key, it's probably in an array. Just include the type.
return $type;
@ -152,13 +177,32 @@ function convert_to_ts($key, $value, $boolisnumber = false, $indentation = '', $
$result .= "[]";
return $result;
} else if ($value == null) {
return "{}; // WARNING: Null structure found";
} else {
echo "WARNING: Unknown structure: $key " . get_class($value) . " \n";
return "";
return "{}; // WARNING: Unknown structure: $key " . get_class($value);
}
}
/**
* Print structure ready to use.
*/
function print_ws_structure($name, $structure, $useparams) {
if ($useparams) {
$type = implode('', array_map('ucfirst', explode('_', $name))) . 'WSParams';
$comment = "Params of $name WS.";
} else {
$type = implode('', array_map('ucfirst', explode('_', $name))) . 'WSResponse';
$comment = "Data returned by $name WS.";
}
echo "
/**
* $comment
*/
export type $type = ".convert_to_ts(null, $structure).";\n";
}
/**
* Concatenate two paths.
*/

View File

@ -1,54 +0,0 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { NgModule } from '@angular/core';
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[] = [
AddonBadgesProvider
];
@NgModule({
declarations: [
],
imports: [
],
providers: [
AddonBadgesProvider,
AddonBadgesUserHandler,
AddonBadgesMyBadgesLinkHandler,
AddonBadgesBadgeLinkHandler,
AddonBadgesPushClickHandler
]
})
export class AddonBadgesModule {
constructor(userDelegate: CoreUserDelegate, userHandler: AddonBadgesUserHandler,
contentLinksDelegate: CoreContentLinksDelegate, myBadgesLinkHandler: AddonBadgesMyBadgesLinkHandler,
badgeLinkHandler: AddonBadgesBadgeLinkHandler,
pushNotificationsDelegate: CorePushNotificationsDelegate, pushClickHandler: AddonBadgesPushClickHandler) {
userDelegate.registerHandler(userHandler);
contentLinksDelegate.registerHandler(myBadgesLinkHandler);
contentLinksDelegate.registerHandler(badgeLinkHandler);
pushNotificationsDelegate.registerClickHandler(pushClickHandler);
}
}

View File

@ -1,179 +0,0 @@
<ion-header>
<ion-navbar core-back-button>
<ion-title>{{badge && badge.name}}</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<ion-refresher [enabled]="badgeLoaded" (ionRefresh)="refreshBadges($event)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<core-loading [hideUntil]="badgeLoaded">
<ion-item-group *ngIf="badge">
<ion-item text-wrap class="item-avatar-center">
<img *ngIf="badge.badgeurl" class="avatar" [src]="badge.badgeurl" core-external-content [alt]="badge.name">
<ion-badge color="danger" *ngIf="badge.dateexpire && currentTime >= badge.dateexpire">
{{ 'addon.badges.expired' | translate }}
</ion-badge>
</ion-item>
</ion-item-group>
<ion-item-group *ngIf="user.fullname">
<ion-item-divider>
<h2>{{ 'addon.badges.recipientdetails' | translate}}</h2>
</ion-item-divider>
<ion-item text-wrap>
<h2>{{ 'core.name' | translate}}</h2>
<p>{{ user.fullname }}</p>
</ion-item>
</ion-item-group>
<ion-item-group *ngIf="badge">
<ion-item-divider>
<h2>{{ 'addon.badges.issuerdetails' | translate}}</h2>
</ion-item-divider>
<ion-item text-wrap *ngIf="badge.issuername">
<h2>{{ 'addon.badges.issuername' | translate}}</h2>
<p>{{ badge.issuername }}</p>
</ion-item>
<ion-item text-wrap *ngIf="badge.issuercontact">
<h2>{{ 'addon.badges.contact' | translate}}</h2>
<p><a href="mailto:{{badge.issuercontact}}" core-link auto-login="no">
{{ badge.issuercontact }}
</a></p>
</ion-item>
</ion-item-group>
<ion-item-group *ngIf="badge">
<ion-item-divider>
<h2>{{ 'addon.badges.badgedetails' | translate}}</h2>
</ion-item-divider>
<ion-item text-wrap *ngIf="badge.name">
<h2>{{ 'core.name' | translate}}</h2>
<p>{{ badge.name }}</p>
</ion-item>
<ion-item text-wrap *ngIf="badge.version">
<h2>{{ 'addon.badges.version' | translate}}</h2>
<p>{{ badge.version }}</p>
</ion-item>
<ion-item text-wrap *ngIf="badge.language">
<h2>{{ 'addon.badges.language' | translate}}</h2>
<p>{{ badge.language }}</p>
</ion-item>
<ion-item text-wrap *ngIf="badge.description">
<h2>{{ 'core.description' | translate}}</h2>
<p>{{ badge.description }}</p>
</ion-item>
<ion-item text-wrap *ngIf="badge.imageauthorname">
<h2>{{ 'addon.badges.imageauthorname' | translate}}</h2>
<p>{{ badge.imageauthorname }}</p>
</ion-item>
<ion-item text-wrap *ngIf="badge.imageauthoremail">
<h2>{{ 'addon.badges.imageauthoremail' | translate}}</h2>
<p><a href="mailto:{{badge.imageauthoremail}}" core-link auto-login="no">
{{ badge.imageauthoremail }}
</a></p>
</ion-item>
<ion-item text-wrap *ngIf="badge.imageauthorurl">
<h2>{{ 'addon.badges.imageauthorurl' | translate}}</h2>
<p><a [href]="badge.imageauthorurl" core-link auto-login="no">
{{ badge.imageauthorurl }}
</a></p>
</ion-item>
<ion-item text-wrap *ngIf="badge.imagecaption">
<h2>{{ 'addon.badges.imagecaption' | translate}}</h2>
<p>{{ badge.imagecaption }}</p>
</ion-item>
<ion-item text-wrap *ngIf="course.fullname">
<h2>{{ 'core.course' | translate}}</h2>
<p>
<core-format-text [text]="course.fullname" contextLevel="course" [contextInstanceId]="courseId"></core-format-text>
</p>
</ion-item>
<!-- Criteria (not yet avalaible) -->
</ion-item-group>
<ion-item-group *ngIf="badge">
<ion-item-divider>
<h2>{{ 'addon.badges.issuancedetails' | translate}}</h2>
</ion-item-divider>
<ion-item text-wrap *ngIf="badge.dateissued">
<h2>{{ 'addon.badges.dateawarded' | translate}}</h2>
<p>{{badge.dateissued * 1000 | coreFormatDate }}</p>
</ion-item>
<ion-item text-wrap *ngIf="badge.dateexpire">
<h2>{{ 'addon.badges.expirydate' | translate}}</h2>
<p>
{{ badge.dateexpire * 1000 | coreFormatDate }}
<span class="text-danger" *ngIf="currentTime >= badge.dateexpire">
{{ 'addon.badges.warnexpired' | translate }}
</span>
</p>
</ion-item>
<!-- Evidence (not yet avalaible) -->
</ion-item-group>
<!-- Endorsement -->
<ion-item-group *ngIf="badge && badge.endorsement">
<ion-item-divider>
<h2>{{ 'addon.badges.bendorsement' | translate}}</h2>
</ion-item-divider>
<ion-item text-wrap *ngIf="badge.endorsement.issuername">
<h2>{{ 'addon.badges.issuername' | translate}}</h2>
<p>{{ badge.endorsement.issuername }}</p>
</ion-item>
<ion-item text-wrap *ngIf="badge.endorsement.issueremail">
<h2>{{ 'addon.badges.issueremail' | translate}}</h2>
<p><a href="mailto:{{badge.endorsement.issueremail}}" core-link auto-login="no">
{{ badge.endorsement.issueremail }}
</a></p>
</ion-item>
<ion-item text-wrap *ngIf="badge.endorsement.issuerurl">
<h2>{{ 'addon.badges.issuerurl' | translate}}</h2>
<p><a [href]="badge.endorsement.issuerurl" core-link auto-login="no">
{{ badge.endorsement.issuerurl }}
</a></p>
</ion-item>
<ion-item text-wrap *ngIf="badge.endorsement.dateissued">
<h2>{{ 'addon.badges.dateawarded' | translate}}</h2>
<p>{{ badge.endorsement.dateissued * 1000 | coreFormatDate }}</p>
</ion-item>
<ion-item text-wrap *ngIf="badge.endorsement.claimid">
<h2>{{ 'addon.badges.claimid' | translate}}</h2>
<p><a [href]="badge.endorsement.claimid" core-link auto-login="no">
{{ badge.endorsement.claimid }}
</a></p>
</ion-item>
<ion-item text-wrap *ngIf="badge.endorsement.claimcomment">
<h2>{{ 'addon.badges.claimcomment' | translate}}</h2>
<p>{{ badge.endorsement.claimcomment }}</p>
</ion-item>
</ion-item-group>
<!-- Related badges -->
<ion-item-group *ngIf="badge && badge.relatedbadges">
<ion-item-divider>
<h2>{{ 'addon.badges.relatedbages' | translate}}</h2>
</ion-item-divider>
<ion-item text-wrap *ngFor="let relatedBadge of badge.relatedbadges">
<h2>{{ relatedBadge.name }}</h2>
</ion-item>
<ion-item text-wrap *ngIf="badge.relatedbadges.length == 0">
<h2>{{ 'addon.badges.norelated' | translate}}</h2>
</ion-item>
</ion-item-group>
<!-- Competencies alignment -->
<ion-item-group *ngIf="badge && badge.alignment">
<ion-item-divider>
<h2>{{ 'addon.badges.alignment' | translate}}</h2>
</ion-item-divider>
<a ion-item text-wrap *ngFor="let alignment of badge.alignment" [href]="alignment.targeturl" core-link auto-login="no">
<h2>{{ alignment.targetname }}</h2>
</a>
<ion-item text-wrap *ngIf="badge.alignment.length == 0">
<h2>{{ 'addon.badges.noalignment' | translate}}</h2>
</ion-item>
</ion-item-group>
</core-loading>
</ion-content>

View File

@ -1,35 +0,0 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { NgModule } from '@angular/core';
import { IonicPageModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreComponentsModule } from '@components/components.module';
import { CoreDirectivesModule } from '@directives/directives.module';
import { CorePipesModule } from '@pipes/pipes.module';
import { AddonBadgesIssuedBadgePage } from './issued-badge';
@NgModule({
declarations: [
AddonBadgesIssuedBadgePage,
],
imports: [
CoreComponentsModule,
CoreDirectivesModule,
CorePipesModule,
IonicPageModule.forChild(AddonBadgesIssuedBadgePage),
TranslateModule.forChild()
],
})
export class AddonBadgesIssuedBadgePageModule {}

View File

@ -1,113 +0,0 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { Component, ViewChild } from '@angular/core';
import { IonicPage, Content, NavParams } from 'ionic-angular';
import { CoreTimeUtilsProvider } from '@providers/utils/time';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreSitesProvider } from '@providers/sites';
import { CoreUserProvider } from '@core/user/providers/user';
import { CoreCoursesProvider } from '@core/courses/providers/courses';
import { AddonBadgesProvider, AddonBadgesUserBadge } from '../../providers/badges';
/**
* Page that displays the list of calendar events.
*/
@IonicPage({ segment: 'addon-badges-issued-badge' })
@Component({
selector: 'page-addon-badges-issued-badge',
templateUrl: 'issued-badge.html',
})
export class AddonBadgesIssuedBadgePage {
@ViewChild(Content) content: Content;
protected badgeHash: string;
protected userId: number;
protected courseId: number;
user: any = {};
course: any = {};
badge: AddonBadgesUserBadge;
badgeLoaded = false;
currentTime = 0;
constructor(private badgesProvider: AddonBadgesProvider, navParams: NavParams, sitesProvider: CoreSitesProvider,
private domUtils: CoreDomUtilsProvider, private timeUtils: CoreTimeUtilsProvider,
private userProvider: CoreUserProvider, private coursesProvider: CoreCoursesProvider) {
this.courseId = navParams.get('courseId') || 0; // Use 0 for site badges.
this.userId = navParams.get('userId') || sitesProvider.getCurrentSite().getUserId();
this.badgeHash = navParams.get('badgeHash');
}
/**
* View loaded.
*/
ionViewDidLoad(): void {
this.fetchIssuedBadge().finally(() => {
this.badgeLoaded = true;
});
}
/**
* Fetch the issued badge required for the view.
*
* @return Promise resolved when done.
*/
fetchIssuedBadge(): Promise<any> {
const promises = [];
this.currentTime = this.timeUtils.timestamp();
promises.push(this.userProvider.getProfile(this.userId, this.courseId, true).then((user) => {
this.user = user;
}));
promises.push(this.badgesProvider.getUserBadges(this.courseId, this.userId).then((badges) => {
const badge = badges.find((badge) => {
return this.badgeHash == badge.uniquehash;
});
if (badge) {
this.badge = badge;
if (badge.courseid) {
return this.coursesProvider.getUserCourse(badge.courseid, true).then((course) => {
this.course = course;
}).catch(() => {
// Maybe an old deleted course.
this.course = null;
});
}
}
}).catch((message) => {
this.domUtils.showErrorModalDefault(message, 'Error getting badge data.');
}));
return Promise.all(promises);
}
/**
* Refresh the badges.
*
* @param refresher Refresher.
*/
refreshBadges(refresher: any): void {
this.badgesProvider.invalidateUserBadges(this.courseId, this.userId).finally(() => {
this.fetchIssuedBadge().finally(() => {
refresher.complete();
});
});
}
}

View File

@ -1,29 +0,0 @@
<ion-header>
<ion-navbar core-back-button>
<ion-title>{{ 'addon.badges.badges' | translate }}</ion-title>
</ion-navbar>
</ion-header>
<core-split-view>
<ion-content>
<ion-refresher [enabled]="badgesLoaded" (ionRefresh)="refreshBadges($event)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<core-loading [hideUntil]="badgesLoaded">
<core-empty-box *ngIf="!badges || badges.length == 0" icon="trophy" [message]="'addon.badges.nobadges' | translate">
</core-empty-box>
<ion-list *ngIf="badges && badges.length" no-margin>
<a ion-item text-wrap *ngFor="let badge of badges" [title]="badge.name" (click)="loadIssuedBadge(badge.uniquehash)" [class.core-split-item-selected]="badge.uniquehash == badgeHash">
<ion-avatar item-start>
<img [src]="badge.badgeurl" [alt]="badge.name" item-start core-external-content>
</ion-avatar>
<h2>{{ badge.name }}</h2>
<p>{{ badge.dateissued * 1000 | coreFormatDate :'strftimedatetimeshort' }}</p>
<ion-badge item-end color="danger" *ngIf="badge.dateexpire && currentTime >= badge.dateexpire">
{{ 'addon.badges.expired' | translate }}
</ion-badge>
</a>
</ion-list>
</core-loading>
</ion-content>
</core-split-view>

View File

@ -1,35 +0,0 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { NgModule } from '@angular/core';
import { IonicPageModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreComponentsModule } from '@components/components.module';
import { CoreDirectivesModule } from '@directives/directives.module';
import { CorePipesModule } from '@pipes/pipes.module';
import { AddonBadgesUserBadgesPage } from './user-badges';
@NgModule({
declarations: [
AddonBadgesUserBadgesPage,
],
imports: [
CoreComponentsModule,
CoreDirectivesModule,
CorePipesModule,
IonicPageModule.forChild(AddonBadgesUserBadgesPage),
TranslateModule.forChild()
],
})
export class AddonBadgesUserBadgesPageModule {}

View File

@ -1,102 +0,0 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { Component, ViewChild } from '@angular/core';
import { IonicPage, Content, NavParams } from 'ionic-angular';
import { AddonBadgesProvider, AddonBadgesUserBadge } from '../../providers/badges';
import { CoreTimeUtilsProvider } from '@providers/utils/time';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreSitesProvider } from '@providers/sites';
import { CoreSplitViewComponent } from '@components/split-view/split-view';
/**
* Page that displays the list of calendar events.
*/
@IonicPage({ segment: 'addon-badges-user-badges' })
@Component({
selector: 'page-addon-badges-user-badges',
templateUrl: 'user-badges.html',
})
export class AddonBadgesUserBadgesPage {
@ViewChild(Content) content: Content;
@ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent;
courseId: number;
userId: number;
badgesLoaded = false;
badges: AddonBadgesUserBadge[] = [];
currentTime = 0;
badgeHash: string;
constructor(navParams: NavParams, sitesProvider: CoreSitesProvider, private badgesProvider: AddonBadgesProvider,
private domUtils: CoreDomUtilsProvider, private timeUtils: CoreTimeUtilsProvider) {
this.courseId = navParams.get('courseId') || 0; // Use 0 for site badges.
this.userId = navParams.get('userId') || sitesProvider.getCurrentSite().getUserId();
}
/**
* View loaded.
*/
ionViewDidLoad(): void {
this.fetchBadges().finally(() => {
if (!this.badgeHash && this.splitviewCtrl.isOn() && this.badges.length > 0) {
// Take first and load it.
this.loadIssuedBadge(this.badges[0].uniquehash);
}
this.badgesLoaded = true;
});
}
/**
* Fetch all the badges required for the view.
*
* @return Promise resolved when done.
*/
fetchBadges(): Promise<any> {
this.currentTime = this.timeUtils.timestamp();
return this.badgesProvider.getUserBadges(this.courseId, this.userId).then((badges) => {
this.badges = badges;
}).catch((message) => {
this.domUtils.showErrorModalDefault(message, 'Error getting badges data.');
});
}
/**
* Refresh the badges.
*
* @param refresher Refresher.
*/
refreshBadges(refresher: any): void {
this.badgesProvider.invalidateUserBadges(this.courseId, this.userId).finally(() => {
this.fetchBadges().finally(() => {
refresher.complete();
});
});
}
/**
* Navigate to a particular badge.
*
* @param badgeHash Badge to load.
*/
loadIssuedBadge(badgeHash: string): void {
this.badgeHash = badgeHash;
const params = {courseId: this.courseId, userId: this.userId, badgeHash: badgeHash};
this.splitviewCtrl.push('AddonBadgesIssuedBadgePage', params);
}
}

View File

@ -1,66 +0,0 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { AddonBadgesProvider } from './badges';
/**
* Handler to treat links to user participants page.
*/
@Injectable()
export class AddonBadgesBadgeLinkHandler extends CoreContentLinksHandlerBase {
name = 'AddonBadgesBadgeLinkHandler';
pattern = /\/badges\/badge\.php.*([\?\&]hash=)/;
constructor(private badgesProvider: AddonBadgesProvider, private linkHelper: CoreContentLinksHelperProvider) {
super();
}
/**
* Get the list of actions for a link (url).
*
* @param siteIds List of sites the URL belongs to.
* @param url The URL to treat.
* @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
* @param courseId Course ID related to the URL. Optional but recommended.
* @return 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, 'AddonBadgesIssuedBadgePage', {courseId: 0, badgeHash: params.hash}, siteId);
}
}];
}
/**
* Check if the handler is enabled for a certain site (site + user) and a URL.
* If not defined, defaults to true.
*
* @param siteId The site ID.
* @param url The URL to treat.
* @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
* @param courseId Course ID related to the URL. Optional but recommended.
* @return Whether the handler is enabled for the URL and site.
*/
isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise<boolean> {
return this.badgesProvider.isPluginEnabled(siteId);
}
}

View File

@ -1,206 +0,0 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { CoreLoggerProvider } from '@providers/logger';
import { CoreSitesProvider } from '@providers/sites';
import { CoreWSExternalWarning } from '@providers/ws';
import { CoreSite } from '@classes/site';
/**
* Service to handle badges.
*/
@Injectable()
export class AddonBadgesProvider {
protected logger;
protected ROOT_CACHE_KEY = 'mmaBadges:';
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider) {
this.logger = logger.getInstance('AddonBadgesProvider');
}
/**
* Returns whether or not the badge plugin is enabled for a certain site.
*
* This method is called quite often and thus should only perform a quick
* check, we should not be calling WS from here.
*
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with true if enabled, false otherwise.
*/
isPluginEnabled(siteId?: string): Promise<boolean> {
return this.sitesProvider.getSite(siteId).then((site) => {
if (!site.canUseAdvancedFeature('enablebadges')) {
return false;
} else if (!site.wsAvailable('core_course_get_user_navigation_options')) {
return false;
}
return true;
});
}
/**
* Get the cache key for the get badges call.
*
* @param courseId ID of the course to get the badges from.
* @param userId ID of the user to get the badges from.
* @return Cache key.
*/
protected getBadgesCacheKey(courseId: number, userId: number): string {
return this.ROOT_CACHE_KEY + 'badges:' + courseId + ':' + userId;
}
/**
* Get issued badges for a certain user in a course.
*
* @param courseId ID of the course to get the badges from.
* @param userId ID of the user to get the badges from.
* @param siteId Site ID. If not defined, current site.
* @return Promise to be resolved when the badges are retrieved.
*/
getUserBadges(courseId: number, userId: number, siteId?: string): Promise<AddonBadgesUserBadge[]> {
this.logger.debug('Get badges for course ' + courseId);
return this.sitesProvider.getSite(siteId).then((site) => {
const data = {
courseid : courseId,
userid : userId
},
preSets = {
cacheKey: this.getBadgesCacheKey(courseId, userId),
updateFrequency: CoreSite.FREQUENCY_RARELY
};
return site.read('core_badges_get_user_badges', data, preSets).then((response: AddonBadgesGetUserBadgesResult): any => {
if (response && response.badges) {
// In 3.7, competencies was renamed to alignment. Rename the property in 3.6 too.
response.badges.forEach((badge) => {
badge.alignment = badge.alignment || badge.competencies;
// Check that the alignment is valid, they were broken in 3.7.
if (badge.alignment && badge.alignment[0] && typeof badge.alignment[0].targetname == 'undefined') {
// If any badge lacks targetname it means they are affected by the Moodle bug, don't display them.
delete badge.alignment;
}
});
return response.badges;
} else {
return Promise.reject(null);
}
});
});
}
/**
* Invalidate get badges WS call.
*
* @param courseId Course ID.
* @param userId ID of the user to get the badges from.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when data is invalidated.
*/
invalidateUserBadges(courseId: number, userId: number, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
return site.invalidateWsCacheForKey(this.getBadgesCacheKey(courseId, userId));
});
}
}
/**
* Result of WS core_badges_get_user_badges.
*/
export type AddonBadgesGetUserBadgesResult = {
badges: AddonBadgesUserBadge[]; // List of badges.
warnings?: CoreWSExternalWarning[]; // List of warnings.
};
/**
* Badge data returned by WS core_badges_get_user_badges.
*/
export type AddonBadgesUserBadge = {
id?: number; // Badge id.
name: string; // Badge name.
description: string; // Badge description.
timecreated?: number; // Time created.
timemodified?: number; // Time modified.
usercreated?: number; // User created.
usermodified?: number; // User modified.
issuername: string; // Issuer name.
issuerurl: string; // Issuer URL.
issuercontact: string; // Issuer contact.
expiredate?: number; // Expire date.
expireperiod?: number; // Expire period.
type?: number; // Type.
courseid?: number; // Course id.
message?: string; // Message.
messagesubject?: string; // Message subject.
attachment?: number; // Attachment.
notification?: number; // @since 3.6. Whether to notify when badge is awarded.
nextcron?: number; // @since 3.6. Next cron.
status?: number; // Status.
issuedid?: number; // Issued id.
uniquehash: string; // Unique hash.
dateissued: number; // Date issued.
dateexpire: number; // Date expire.
visible?: number; // Visible.
email?: string; // @since 3.6. User email.
version?: string; // @since 3.6. Version.
language?: string; // @since 3.6. Language.
imageauthorname?: string; // @since 3.6. Name of the image author.
imageauthoremail?: string; // @since 3.6. Email of the image author.
imageauthorurl?: string; // @since 3.6. URL of the image author.
imagecaption?: string; // @since 3.6. Caption of the image.
badgeurl: string; // Badge URL.
endorsement?: { // @since 3.6.
id: number; // Endorsement id.
badgeid: number; // Badge id.
issuername: string; // Endorsement issuer name.
issuerurl: string; // Endorsement issuer URL.
issueremail: string; // Endorsement issuer email.
claimid: string; // Claim URL.
claimcomment: string; // Claim comment.
dateissued: number; // Date issued.
};
alignment?: { // @since 3.7. Calculated by the app for 3.6 sites. Badge alignments.
id?: number; // Alignment id.
badgeid?: number; // Badge id.
targetname?: string; // Target name.
targeturl?: string; // Target URL.
targetdescription?: string; // Target description.
targetframework?: string; // Target framework.
targetcode?: string; // Target code.
}[];
competencies?: { // @deprecated from 3.7. @since 3.6. In 3.7 it was renamed to alignment.
id?: number; // Alignment id.
badgeid?: number; // Badge id.
targetname?: string; // Target name.
targeturl?: string; // Target URL.
targetdescription?: string; // Target description.
targetframework?: string; // Target framework.
targetcode?: string; // Target code.
}[];
relatedbadges?: { // @since 3.6. Related badges.
id: number; // Badge id.
name: string; // Badge name.
version?: string; // Version.
language?: string; // Language.
type?: number; // Type.
}[];
};

View File

@ -1,67 +0,0 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { AddonBadgesProvider } from './badges';
/**
* Handler to treat links to user badges page.
*/
@Injectable()
export class AddonBadgesMyBadgesLinkHandler extends CoreContentLinksHandlerBase {
name = 'AddonBadgesMyBadgesLinkHandler';
featureName = 'CoreUserDelegate_AddonBadges';
pattern = /\/badges\/mybadges\.php/;
constructor(private badgesProvider: AddonBadgesProvider, private linkHelper: CoreContentLinksHelperProvider) {
super();
}
/**
* Get the list of actions for a link (url).
*
* @param siteIds List of sites the URL belongs to.
* @param url The URL to treat.
* @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
* @param courseId Course ID related to the URL. Optional but recommended.
* @return 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, 'AddonBadgesUserBadgesPage', {}, siteId);
}
}];
}
/**
* Check if the handler is enabled for a certain site (site + user) and a URL.
* If not defined, defaults to true.
*
* @param siteId The site ID.
* @param url The URL to treat.
* @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
* @param courseId Course ID related to the URL. Optional but recommended.
* @return Whether the handler is enabled for the URL and site.
*/
isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise<boolean> {
return this.badgesProvider.isPluginEnabled(siteId);
}
}

View File

@ -1,71 +0,0 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 notification The notification to check.
* @return 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 notification The notification to check.
* @return 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,75 +0,0 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { CoreUserDelegate, CoreUserProfileHandler, CoreUserProfileHandlerData } from '@core/user/providers/user-delegate';
import { AddonBadgesProvider } from './badges';
/**
* Profile badges handler.
*/
@Injectable()
export class AddonBadgesUserHandler implements CoreUserProfileHandler {
name = 'AddonBadges';
priority = 50;
type = CoreUserDelegate.TYPE_NEW_PAGE;
constructor(protected badgesProvider: AddonBadgesProvider) { }
/**
* Check if handler is enabled.
*
* @return Always enabled.
*/
isEnabled(): Promise<boolean> {
return this.badgesProvider.isPluginEnabled();
}
/**
* Check if handler is enabled for this user in this context.
*
* @param user User to check.
* @param courseId Course ID.
* @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions.
* @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
* @return True if enabled, false otherwise.
*/
isEnabledForUser(user: any, courseId: number, navOptions?: any, admOptions?: any): boolean {
if (navOptions && typeof navOptions.badges != 'undefined') {
return navOptions.badges;
}
// If we reach here, it means we are opening the user site profile.
return true;
}
/**
* Returns the data needed to render the handler.
*
* @return Data needed to render the handler.
*/
getDisplayData(user: any, courseId: number): CoreUserProfileHandlerData {
return {
icon: 'trophy',
title: 'addon.badges.badges',
class: '',
action: (event, navCtrl, user, courseId): void => {
event.preventDefault();
event.stopPropagation();
navCtrl.push('AddonBadgesUserBadgesPage', {courseId: courseId, userId: user.id });
}
};
}
}

View File

@ -1,44 +0,0 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreComponentsModule } from '@components/components.module';
import { CoreDirectivesModule } from '@directives/directives.module';
import { AddonBlockActivityModulesComponentsModule } from './components/components.module';
import { CoreBlockDelegate } from '@core/block/providers/delegate';
import { AddonBlockActivityModulesHandler } from './providers/block-handler';
@NgModule({
declarations: [
],
imports: [
IonicModule,
CoreComponentsModule,
CoreDirectivesModule,
AddonBlockActivityModulesComponentsModule,
TranslateModule.forChild()
],
exports: [
],
providers: [
AddonBlockActivityModulesHandler
]
})
export class AddonBlockActivityModulesModule {
constructor(blockDelegate: CoreBlockDelegate, blockHandler: AddonBlockActivityModulesHandler) {
blockDelegate.registerHandler(blockHandler);
}
}

View File

@ -1,17 +0,0 @@
ion-app.app-root.md addon-block-activitymodules {
.core-module-icon {
margin-top: $label-md-margin-top;
margin-bottom: $label-md-margin-bottom;
width: 24px;
height: 24px;
}
}
ion-app.app-root.ios addon-block-activitymodules {
.core-module-icon {
margin-top: $label-ios-margin-top;
margin-bottom: $label-ios-margin-bottom;
width: 24px;
height: 24px;
}
}

View File

@ -1,140 +0,0 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { Component, OnInit, Injector, Input } from '@angular/core';
import { CoreCourseProvider } from '@core/course/providers/course';
import { CoreCourseModuleDelegate } from '@core/course/providers/module-delegate';
import { CoreBlockBaseComponent } from '@core/block/classes/base-block-component';
import { CoreConstants, ContextLevel } from '@core/constants';
import { TranslateService } from '@ngx-translate/core';
import { CoreSitesProvider } from '@providers/sites';
/**
* Component to render an "activity modules" block.
*/
@Component({
selector: 'addon-block-activitymodules',
templateUrl: 'addon-block-activitymodules.html'
})
export class AddonBlockActivityModulesComponent extends CoreBlockBaseComponent implements OnInit {
@Input() block: any; // The block to render.
@Input() contextLevel: ContextLevel; // The context where the block will be used.
@Input() instanceId: number; // The instance ID associated with the context level.
entries: any[] = [];
protected fetchContentDefaultError = 'Error getting activity modules data.';
constructor(injector: Injector, protected courseProvider: CoreCourseProvider,
protected translate: TranslateService, protected moduleDelegate: CoreCourseModuleDelegate,
protected sitesProvider: CoreSitesProvider) {
super(injector, 'AddonBlockActivityModulesComponent');
}
/**
* Component being initialized.
*/
ngOnInit(): void {
super.ngOnInit();
}
/**
* Perform the invalidate content function.
*
* @return Resolved when done.
*/
protected invalidateContent(): Promise<any> {
return this.courseProvider.invalidateSections(this.instanceId);
}
/**
* Fetch the data to render the block.
*
* @return Promise resolved when done.
*/
protected fetchContent(): Promise<any> {
return this.courseProvider.getSections(this.getCourseId(), false, true).then((sections) => {
this.entries = [];
const archetypes = {},
modIcons = {};
let modFullNames = {};
sections.forEach((section) => {
if (!section.modules) {
return;
}
section.modules.forEach((mod) => {
if (mod.uservisible === false || !this.courseProvider.moduleHasView(mod) ||
typeof modFullNames[mod.modname] != 'undefined') {
// Ignore this module.
return;
}
// Get the archetype of the module type.
if (typeof archetypes[mod.modname] == 'undefined') {
archetypes[mod.modname] = this.moduleDelegate.supportsFeature(mod.modname,
CoreConstants.FEATURE_MOD_ARCHETYPE, CoreConstants.MOD_ARCHETYPE_OTHER);
}
// Get the full name of the module type.
if (archetypes[mod.modname] == CoreConstants.MOD_ARCHETYPE_RESOURCE) {
// All resources are gathered in a single "Resources" option.
if (!modFullNames['resources']) {
modFullNames['resources'] = this.translate.instant('core.resources');
}
} else {
modFullNames[mod.modname] = mod.modplural;
}
modIcons[mod.modname] = mod.modicon;
});
});
// Sort the modnames alphabetically.
modFullNames = this.utils.sortValues(modFullNames);
for (const modName in modFullNames) {
let icon;
if (modName === 'resources') {
icon = this.courseProvider.getModuleIconSrc('page', modIcons['page']);
} else {
icon = this.moduleDelegate.getModuleIconSrc(modName, modIcons[modName]);
}
this.entries.push({
icon: icon,
name: modFullNames[modName],
modName: modName
});
}
});
}
/**
* Obtain the appropiate course id for the block.
*
* @return Course id.
*/
protected getCourseId(): number {
switch (this.contextLevel) {
case ContextLevel.COURSE:
return this.instanceId;
default:
return this.sitesProvider.getCurrentSiteHomeId();
}
}
}

View File

@ -1,9 +0,0 @@
<ion-item-divider>
<h2>{{ 'addon.block_activitymodules.pluginname' | translate }}</h2>
</ion-item-divider>
<core-loading [hideUntil]="loaded" class="core-loading-center">
<a ion-item text-wrap *ngFor="let entry of entries" class="item-media" detail-none navPush="CoreCourseListModTypePage" [navParams]="{title: entry.name, courseId: instanceId, modName: entry.modName}">
<img item-start [src]="entry.icon" alt="" role="presentation" class="core-module-icon">
{{ entry.name }}
</a>
</core-loading>

View File

@ -1,45 +0,0 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IonicModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { AddonBlockActivityModulesComponent } from './activitymodules/activitymodules';
import { CoreComponentsModule } from '@components/components.module';
import { CoreDirectivesModule } from '@directives/directives.module';
import { CoreCourseComponentsModule } from '@core/course/components/components.module';
@NgModule({
declarations: [
AddonBlockActivityModulesComponent
],
imports: [
CommonModule,
IonicModule,
TranslateModule.forChild(),
CoreComponentsModule,
CoreDirectivesModule,
CoreCourseComponentsModule
],
providers: [
],
exports: [
AddonBlockActivityModulesComponent
],
entryComponents: [
AddonBlockActivityModulesComponent
]
})
export class AddonBlockActivityModulesComponentsModule {}

View File

@ -1,50 +0,0 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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, Injector } from '@angular/core';
import { CoreBlockHandlerData } from '@core/block/providers/delegate';
import { AddonBlockActivityModulesComponent } from '../components/activitymodules/activitymodules';
import { CoreBlockBaseHandler } from '@core/block/classes/base-block-handler';
/**
* Block handler.
*/
@Injectable()
export class AddonBlockActivityModulesHandler extends CoreBlockBaseHandler {
name = 'AddonBlockActivityModules';
blockName = 'activity_modules';
constructor() {
super();
}
/**
* Returns the data needed to render the block.
*
* @param injector Injector.
* @param block The block to render.
* @param contextLevel The context where the block will be used.
* @param instanceId The instance ID associated with the context level.
* @return Data or promise resolved with the data.
*/
getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number)
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
return {
title: 'addon.block_activitymodules.pluginname',
class: 'addon-block-activitymodules',
component: AddonBlockActivityModulesComponent
};
}
}

View File

@ -1,38 +0,0 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreBlockDelegate } from '@core/block/providers/delegate';
import { AddonBlockActivityResultsHandler } from './providers/block-handler';
@NgModule({
declarations: [
],
imports: [
IonicModule,
TranslateModule.forChild()
],
exports: [
],
providers: [
AddonBlockActivityResultsHandler
]
})
export class AddonBlockActivityResultsModule {
constructor(blockDelegate: CoreBlockDelegate, blockHandler: AddonBlockActivityResultsHandler) {
blockDelegate.registerHandler(blockHandler);
}
}

View File

@ -1,31 +0,0 @@
.addon-block-activity-results core-block-pre-rendered {
ion-item.core-block-content {
table.grades {
@include text-align('start');
width: 100%;
.number {
@include text-align('start');
width: 10%;
}
.name {
@include text-align('start');
width: 77%;
}
.grade {
@include text-align('end');
}
caption {
@include text-align('start');
padding-top: .75rem;
padding-bottom: .75rem;
color: $gray-darker;
font-weight: bold;
font-size: 18px;
}
}
}
}

View File

@ -1,51 +0,0 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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, Injector } from '@angular/core';
import { CoreBlockHandlerData } from '@core/block/providers/delegate';
import { CoreBlockPreRenderedComponent } from '@core/block/components/pre-rendered-block/pre-rendered-block';
import { CoreBlockBaseHandler } from '@core/block/classes/base-block-handler';
/**
* Block handler.
*/
@Injectable()
export class AddonBlockActivityResultsHandler extends CoreBlockBaseHandler {
name = 'AddonBlockActivityResults';
blockName = 'activity_results';
constructor() {
super();
}
/**
* Returns the data needed to render the block.
*
* @param injector Injector.
* @param block The block to render.
* @param contextLevel The context where the block will be used.
* @param instanceId The instance ID associated with the context level.
* @return Data or promise resolved with the data.
*/
getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number)
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
return {
title: 'addon.block_activityresults.pluginname',
class: 'addon-block-activity-results',
component: CoreBlockPreRenderedComponent
};
}
}

View File

@ -1,38 +0,0 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreBlockDelegate } from '@core/block/providers/delegate';
import { AddonBlockBadgesHandler } from './providers/block-handler';
@NgModule({
declarations: [
],
imports: [
IonicModule,
TranslateModule.forChild()
],
exports: [
],
providers: [
AddonBlockBadgesHandler
]
})
export class AddonBlockBadgesModule {
constructor(blockDelegate: CoreBlockDelegate, blockHandler: AddonBlockBadgesHandler) {
blockDelegate.registerHandler(blockHandler);
}
}

View File

@ -1,23 +0,0 @@
.addon-block-badges core-block-pre-rendered {
.core-block-content {
ul.badges {
list-style: none;
@include margin-horizontal(0);
-webkit-padding-start: 0;
li {
position: relative;
display: inline-block;
padding-top: 1em;
text-align: center;
vertical-align: top;
width: 150px;
.badge-name {
display: block;
padding: 5px;
}
}
}
}
}

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