forked from EVOgeek/Vmeda.Online
		
	Merge pull request #2557 from NoelDeMartin/MOBILE-3320
MOBILE-3320 Updated linting rules + add Error tests
This commit is contained in:
		
						commit
						1ab5c78491
					
				
							
								
								
									
										102
									
								
								.eslintrc.js
									
									
									
									
									
								
							
							
						
						
									
										102
									
								
								.eslintrc.js
									
									
									
									
									
								
							| @ -1,33 +1,30 @@ | |||||||
| module.exports = { | var appConfig = { | ||||||
|     root: true, |  | ||||||
|     overrides: [ |  | ||||||
|         { |  | ||||||
|             files: ['*.ts'], |  | ||||||
|     env: { |     env: { | ||||||
|         browser: true, |         browser: true, | ||||||
|         es6: true, |         es6: true, | ||||||
|         node: true, |         node: true, | ||||||
|     }, |     }, | ||||||
|  |     plugins: [ | ||||||
|  |         '@typescript-eslint', | ||||||
|  |         'header', | ||||||
|  |         'jsdoc', | ||||||
|  |         'prefer-arrow', | ||||||
|  |         'promise', | ||||||
|  |     ], | ||||||
|     extends: [ |     extends: [ | ||||||
|         'eslint:recommended', |         'eslint:recommended', | ||||||
|         'plugin:@typescript-eslint/recommended', |         'plugin:@typescript-eslint/recommended', | ||||||
|         'prettier', |         'prettier', | ||||||
|         'prettier/@typescript-eslint', |         'prettier/@typescript-eslint', | ||||||
|                 'plugin:jest/recommended', |  | ||||||
|         'plugin:@angular-eslint/recommended', |         'plugin:@angular-eslint/recommended', | ||||||
|  |         'plugin:promise/recommended', | ||||||
|     ], |     ], | ||||||
|     parser: '@typescript-eslint/parser', |     parser: '@typescript-eslint/parser', | ||||||
|     parserOptions: { |     parserOptions: { | ||||||
|         project: 'tsconfig.json', |         project: 'tsconfig.json', | ||||||
|         sourceType: 'module', |         sourceType: 'module', | ||||||
|     }, |     }, | ||||||
|             plugins: [ |     reportUnusedDisableDirectives: true, | ||||||
|                 'eslint-plugin-prefer-arrow', |  | ||||||
|                 'eslint-plugin-jsdoc', |  | ||||||
|                 '@typescript-eslint', |  | ||||||
|                 'header', |  | ||||||
|                 'jest', |  | ||||||
|             ], |  | ||||||
|     rules: { |     rules: { | ||||||
|         '@angular-eslint/component-class-suffix': ['error', { suffixes: ['Component', 'Page'] }], |         '@angular-eslint/component-class-suffix': ['error', { suffixes: ['Component', 'Page'] }], | ||||||
|         '@typescript-eslint/adjacent-overload-signatures': 'error', |         '@typescript-eslint/adjacent-overload-signatures': 'error', | ||||||
| @ -56,7 +53,29 @@ module.exports = { | |||||||
|                 accessibility: 'no-public', |                 accessibility: 'no-public', | ||||||
|             }, |             }, | ||||||
|         ], |         ], | ||||||
|                 '@typescript-eslint/indent': 'error', |         '@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': [ |         '@typescript-eslint/member-delimiter-style': [ | ||||||
|             'error', |             'error', | ||||||
|             { |             { | ||||||
| @ -85,19 +104,14 @@ module.exports = { | |||||||
|         ], |         ], | ||||||
|         '@typescript-eslint/no-empty-function': 'error', |         '@typescript-eslint/no-empty-function': 'error', | ||||||
|         '@typescript-eslint/no-empty-interface': 'off', |         '@typescript-eslint/no-empty-interface': 'off', | ||||||
|                 '@typescript-eslint/no-explicit-any': [ |         '@typescript-eslint/no-explicit-any': 'warn', | ||||||
|                     'warn', |  | ||||||
|                     { |  | ||||||
|                         fixToUnknown: true, |  | ||||||
|                     }, |  | ||||||
|                 ], |  | ||||||
|         '@typescript-eslint/no-inferrable-types': [ |         '@typescript-eslint/no-inferrable-types': [ | ||||||
|             'error', |             'error', | ||||||
|             { |             { | ||||||
|                 ignoreParameters: true, |                 ignoreParameters: true, | ||||||
|             }, |             }, | ||||||
|         ], |         ], | ||||||
|                 '@typescript-eslint/no-non-null-assertion': 'error', |         '@typescript-eslint/no-non-null-assertion': 'off', | ||||||
|         '@typescript-eslint/no-this-alias': 'error', |         '@typescript-eslint/no-this-alias': 'error', | ||||||
|         '@typescript-eslint/no-unused-vars': 'error', |         '@typescript-eslint/no-unused-vars': 'error', | ||||||
|         '@typescript-eslint/quotes': [ |         '@typescript-eslint/quotes': [ | ||||||
| @ -109,18 +123,6 @@ module.exports = { | |||||||
|             'always', |             'always', | ||||||
|         ], |         ], | ||||||
|         '@typescript-eslint/type-annotation-spacing': 'error', |         '@typescript-eslint/type-annotation-spacing': 'error', | ||||||
|                 '@typescript-eslint/typedef': [ |  | ||||||
|                     'error', |  | ||||||
|                     { |  | ||||||
|                         arrayDestructuring: false, |  | ||||||
|                         arrowParameter: false, |  | ||||||
|                         memberVariableDeclaration: true, |  | ||||||
|                         objectDestructuring: false, |  | ||||||
|                         parameter: true, |  | ||||||
|                         propertyDeclaration: true, |  | ||||||
|                         variableDeclaration: false, |  | ||||||
|                     }, |  | ||||||
|                 ], |  | ||||||
|         '@typescript-eslint/unified-signatures': 'error', |         '@typescript-eslint/unified-signatures': 'error', | ||||||
|         'header/header': [ |         'header/header': [ | ||||||
|             2, |             2, | ||||||
| @ -142,13 +144,20 @@ module.exports = { | |||||||
|             ], |             ], | ||||||
|             1, |             1, | ||||||
|         ], |         ], | ||||||
|  |         'promise/catch-or-return': [ | ||||||
|  |             'warn', | ||||||
|  |             { | ||||||
|  |                 allowFinally: true, | ||||||
|  |             }, | ||||||
|  |         ], | ||||||
|         'arrow-body-style': ['error', 'as-needed'], |         'arrow-body-style': ['error', 'as-needed'], | ||||||
|         'array-bracket-spacing': ['error', 'never'], |         'array-bracket-spacing': ['error', 'never'], | ||||||
|         'comma-dangle': ['error', 'always-multiline'], |         'comma-dangle': ['error', 'always-multiline'], | ||||||
|         'constructor-super': 'error', |         'constructor-super': 'error', | ||||||
|         'curly': 'error', |         'curly': 'error', | ||||||
|                 'default-case': 'error', |  | ||||||
|         'eol-last': 'error', |         'eol-last': 'error', | ||||||
|  |         'function-call-argument-newline': ['error', 'consistent'], | ||||||
|  |         'function-paren-newline': ['error', 'multiline-arguments'], | ||||||
|         'id-blacklist': [ |         'id-blacklist': [ | ||||||
|             'error', |             'error', | ||||||
|             'any', |             'any', | ||||||
| @ -163,13 +172,17 @@ module.exports = { | |||||||
|         ], |         ], | ||||||
|         'id-match': 'error', |         'id-match': 'error', | ||||||
|         'jsdoc/check-alignment': 'error', |         'jsdoc/check-alignment': 'error', | ||||||
|                 'jsdoc/check-indentation': 'error', |         'jsdoc/check-indentation': [ | ||||||
|  |             'error', | ||||||
|  |             { | ||||||
|  |                 excludeTags: ['param'], | ||||||
|  |             }, | ||||||
|  |         ], | ||||||
|         'jsdoc/newline-after-description': 'error', |         'jsdoc/newline-after-description': 'error', | ||||||
|         'linebreak-style': [ |         'linebreak-style': [ | ||||||
|             'error', |             'error', | ||||||
|             'unix', |             'unix', | ||||||
|         ], |         ], | ||||||
|                 'lines-between-class-members': ['error', 'always'], |  | ||||||
|         'max-len': [ |         'max-len': [ | ||||||
|             'error', |             'error', | ||||||
|             { |             { | ||||||
| @ -238,7 +251,24 @@ module.exports = { | |||||||
|         'use-isnan': 'error', |         'use-isnan': 'error', | ||||||
|         'yoda': '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'], |             files: ['*.html'], | ||||||
|             extends: ['plugin:@angular-eslint/template/recommended'], |             extends: ['plugin:@angular-eslint/template/recommended'], | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -22,7 +22,6 @@ npm-debug.log* | |||||||
| /.sass-cache | /.sass-cache | ||||||
| /.sourcemaps | /.sourcemaps | ||||||
| /.versions | /.versions | ||||||
| /.vscode |  | ||||||
| /coverage | /coverage | ||||||
| /dist | /dist | ||||||
| /node_modules | /node_modules | ||||||
|  | |||||||
							
								
								
									
										35
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
										Normal 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", | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     ] | ||||||
|  | } | ||||||
							
								
								
									
										145
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										145
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -3849,12 +3849,6 @@ | |||||||
|         "picomatch": "^2.0.4" |         "picomatch": "^2.0.4" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "app-root-path": { |  | ||||||
|       "version": "3.0.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.0.0.tgz", |  | ||||||
|       "integrity": "sha512-qMcx+Gy2UZynHjOHOIXPNvpf+9cjvk3cWrBBK7zg4gH9+clobJRb9NGzcT7mQTcV/6Gm/1WelUtqxVXnNlrwcw==", |  | ||||||
|       "dev": true |  | ||||||
|     }, |  | ||||||
|     "aproba": { |     "aproba": { | ||||||
|       "version": "1.2.0", |       "version": "1.2.0", | ||||||
|       "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", |       "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", | ||||||
| @ -3876,16 +3870,6 @@ | |||||||
|         "sprintf-js": "~1.0.2" |         "sprintf-js": "~1.0.2" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "aria-query": { |  | ||||||
|       "version": "3.0.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz", |  | ||||||
|       "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=", |  | ||||||
|       "dev": true, |  | ||||||
|       "requires": { |  | ||||||
|         "ast-types-flow": "0.0.7", |  | ||||||
|         "commander": "^2.11.0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "arity-n": { |     "arity-n": { | ||||||
|       "version": "1.0.4", |       "version": "1.0.4", | ||||||
|       "resolved": "https://registry.npmjs.org/arity-n/-/arity-n-1.0.4.tgz", |       "resolved": "https://registry.npmjs.org/arity-n/-/arity-n-1.0.4.tgz", | ||||||
| @ -4059,12 +4043,6 @@ | |||||||
|       "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", |       "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|     "ast-types-flow": { |  | ||||||
|       "version": "0.0.7", |  | ||||||
|       "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", |  | ||||||
|       "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", |  | ||||||
|       "dev": true |  | ||||||
|     }, |  | ||||||
|     "astral-regex": { |     "astral-regex": { | ||||||
|       "version": "1.0.0", |       "version": "1.0.0", | ||||||
|       "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", |       "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", | ||||||
| @ -4184,15 +4162,6 @@ | |||||||
|       "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz", |       "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz", | ||||||
|       "integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==" |       "integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==" | ||||||
|     }, |     }, | ||||||
|     "axobject-query": { |  | ||||||
|       "version": "2.0.2", |  | ||||||
|       "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz", |  | ||||||
|       "integrity": "sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww==", |  | ||||||
|       "dev": true, |  | ||||||
|       "requires": { |  | ||||||
|         "ast-types-flow": "0.0.7" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "babel-jest": { |     "babel-jest": { | ||||||
|       "version": "26.5.2", |       "version": "26.5.2", | ||||||
|       "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.5.2.tgz", |       "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.5.2.tgz", | ||||||
| @ -5291,60 +5260,6 @@ | |||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "codelyzer": { |  | ||||||
|       "version": "6.0.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-6.0.1.tgz", |  | ||||||
|       "integrity": "sha512-cOyGQgMdhnRYtW2xrJUNrNYDjEgwQ+BrE2y93Bwz3h4DJ6vJRLfupemU5N3pbYsUlBHJf0u1j1UGk+NLW4d97g==", |  | ||||||
|       "dev": true, |  | ||||||
|       "requires": { |  | ||||||
|         "@angular/compiler": "9.0.0", |  | ||||||
|         "@angular/core": "9.0.0", |  | ||||||
|         "app-root-path": "^3.0.0", |  | ||||||
|         "aria-query": "^3.0.0", |  | ||||||
|         "axobject-query": "2.0.2", |  | ||||||
|         "css-selector-tokenizer": "^0.7.1", |  | ||||||
|         "cssauron": "^1.4.0", |  | ||||||
|         "damerau-levenshtein": "^1.0.4", |  | ||||||
|         "rxjs": "^6.5.3", |  | ||||||
|         "semver-dsl": "^1.0.1", |  | ||||||
|         "source-map": "^0.5.7", |  | ||||||
|         "sprintf-js": "^1.1.2", |  | ||||||
|         "tslib": "^1.10.0", |  | ||||||
|         "zone.js": "~0.10.3" |  | ||||||
|       }, |  | ||||||
|       "dependencies": { |  | ||||||
|         "@angular/compiler": { |  | ||||||
|           "version": "9.0.0", |  | ||||||
|           "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-9.0.0.tgz", |  | ||||||
|           "integrity": "sha512-ctjwuntPfZZT2mNj2NDIVu51t9cvbhl/16epc5xEwyzyDt76pX9UgwvY+MbXrf/C/FWwdtmNtfP698BKI+9leQ==", |  | ||||||
|           "dev": true |  | ||||||
|         }, |  | ||||||
|         "@angular/core": { |  | ||||||
|           "version": "9.0.0", |  | ||||||
|           "resolved": "https://registry.npmjs.org/@angular/core/-/core-9.0.0.tgz", |  | ||||||
|           "integrity": "sha512-6Pxgsrf0qF9iFFqmIcWmjJGkkCaCm6V5QNnxMy2KloO3SDq6QuMVRbN9RtC8Urmo25LP+eZ6ZgYqFYpdD8Hd9w==", |  | ||||||
|           "dev": true |  | ||||||
|         }, |  | ||||||
|         "source-map": { |  | ||||||
|           "version": "0.5.7", |  | ||||||
|           "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", |  | ||||||
|           "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", |  | ||||||
|           "dev": true |  | ||||||
|         }, |  | ||||||
|         "sprintf-js": { |  | ||||||
|           "version": "1.1.2", |  | ||||||
|           "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", |  | ||||||
|           "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", |  | ||||||
|           "dev": true |  | ||||||
|         }, |  | ||||||
|         "tslib": { |  | ||||||
|           "version": "1.14.0", |  | ||||||
|           "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.0.tgz", |  | ||||||
|           "integrity": "sha512-+Zw5lu0D9tvBMjGP8LpvMb0u2WW2QV3y+D8mO6J+cNzCYIN4sVy43Bf9vl92nqFahutN0I8zHa7cc4vihIshnw==", |  | ||||||
|           "dev": true |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "collect-v8-coverage": { |     "collect-v8-coverage": { | ||||||
|       "version": "1.0.1", |       "version": "1.0.1", | ||||||
|       "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", |       "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", | ||||||
| @ -6559,16 +6474,6 @@ | |||||||
|       "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==", |       "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|     "css-selector-tokenizer": { |  | ||||||
|       "version": "0.7.3", |  | ||||||
|       "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.3.tgz", |  | ||||||
|       "integrity": "sha512-jWQv3oCEL5kMErj4wRnK/OPoBi0D+P1FR2cDCKYPaMeD2eW3/mttav8HT4hT1CKopiJI/psEULjkClhvJo4Lvg==", |  | ||||||
|       "dev": true, |  | ||||||
|       "requires": { |  | ||||||
|         "cssesc": "^3.0.0", |  | ||||||
|         "fastparse": "^1.1.2" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "css-tree": { |     "css-tree": { | ||||||
|       "version": "1.0.0-alpha.37", |       "version": "1.0.0-alpha.37", | ||||||
|       "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", |       "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", | ||||||
| @ -6593,15 +6498,6 @@ | |||||||
|       "integrity": "sha512-wHOppVDKl4vTAOWzJt5Ek37Sgd9qq1Bmj/T1OjvicWbU5W7ru7Pqbn0Jdqii3Drx/h+dixHKXNhZYx7blthL7g==", |       "integrity": "sha512-wHOppVDKl4vTAOWzJt5Ek37Sgd9qq1Bmj/T1OjvicWbU5W7ru7Pqbn0Jdqii3Drx/h+dixHKXNhZYx7blthL7g==", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|     "cssauron": { |  | ||||||
|       "version": "1.4.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/cssauron/-/cssauron-1.4.0.tgz", |  | ||||||
|       "integrity": "sha1-pmAt/34EqDBtwNuaVR6S6LVmKtg=", |  | ||||||
|       "dev": true, |  | ||||||
|       "requires": { |  | ||||||
|         "through": "X.X.X" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "cssesc": { |     "cssesc": { | ||||||
|       "version": "3.0.0", |       "version": "3.0.0", | ||||||
|       "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", |       "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", | ||||||
| @ -6765,12 +6661,6 @@ | |||||||
|         "type": "^1.0.1" |         "type": "^1.0.1" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "damerau-levenshtein": { |  | ||||||
|       "version": "1.0.6", |  | ||||||
|       "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz", |  | ||||||
|       "integrity": "sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug==", |  | ||||||
|       "dev": true |  | ||||||
|     }, |  | ||||||
|     "dashdash": { |     "dashdash": { | ||||||
|       "version": "1.14.1", |       "version": "1.14.1", | ||||||
|       "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", |       "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", | ||||||
| @ -7936,6 +7826,12 @@ | |||||||
|       "integrity": "sha512-C8YMhL+r8RMeMdYAw/rQtE6xNdMulj+zGWud/qIGnlmomiPRaLDGLMeskZ3alN6uMBojmooRimtdrXebLN4svQ==", |       "integrity": "sha512-C8YMhL+r8RMeMdYAw/rQtE6xNdMulj+zGWud/qIGnlmomiPRaLDGLMeskZ3alN6uMBojmooRimtdrXebLN4svQ==", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|  |     "eslint-plugin-promise": { | ||||||
|  |       "version": "4.2.1", | ||||||
|  |       "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz", | ||||||
|  |       "integrity": "sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==", | ||||||
|  |       "dev": true | ||||||
|  |     }, | ||||||
|     "eslint-scope": { |     "eslint-scope": { | ||||||
|       "version": "4.0.3", |       "version": "4.0.3", | ||||||
|       "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", |       "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", | ||||||
| @ -8329,6 +8225,12 @@ | |||||||
|       "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.0.tgz", |       "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.0.tgz", | ||||||
|       "integrity": "sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=" |       "integrity": "sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=" | ||||||
|     }, |     }, | ||||||
|  |     "faker": { | ||||||
|  |       "version": "5.1.0", | ||||||
|  |       "resolved": "https://registry.npmjs.org/faker/-/faker-5.1.0.tgz", | ||||||
|  |       "integrity": "sha512-RrWKFSSA/aNLP0g3o2WW1Zez7/MnMr7xkiZmoCfAGZmdkDQZ6l2KtuXHN5XjdvpRjDl8+3vf+Rrtl06Z352+Mw==", | ||||||
|  |       "dev": true | ||||||
|  |     }, | ||||||
|     "fast-deep-equal": { |     "fast-deep-equal": { | ||||||
|       "version": "3.1.3", |       "version": "3.1.3", | ||||||
|       "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", |       "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", | ||||||
| @ -8363,12 +8265,6 @@ | |||||||
|       "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", |       "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|     "fastparse": { |  | ||||||
|       "version": "1.1.2", |  | ||||||
|       "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", |  | ||||||
|       "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", |  | ||||||
|       "dev": true |  | ||||||
|     }, |  | ||||||
|     "fastq": { |     "fastq": { | ||||||
|       "version": "1.8.0", |       "version": "1.8.0", | ||||||
|       "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz", |       "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz", | ||||||
| @ -15642,23 +15538,6 @@ | |||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "semver-dsl": { |  | ||||||
|       "version": "1.0.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/semver-dsl/-/semver-dsl-1.0.1.tgz", |  | ||||||
|       "integrity": "sha1-02eN5VVeimH2Ke7QJTZq5fJzQKA=", |  | ||||||
|       "dev": true, |  | ||||||
|       "requires": { |  | ||||||
|         "semver": "^5.3.0" |  | ||||||
|       }, |  | ||||||
|       "dependencies": { |  | ||||||
|         "semver": { |  | ||||||
|           "version": "5.7.1", |  | ||||||
|           "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", |  | ||||||
|           "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", |  | ||||||
|           "dev": true |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "semver-intersect": { |     "semver-intersect": { | ||||||
|       "version": "1.4.0", |       "version": "1.4.0", | ||||||
|       "resolved": "https://registry.npmjs.org/semver-intersect/-/semver-intersect-1.4.0.tgz", |       "resolved": "https://registry.npmjs.org/semver-intersect/-/semver-intersect-1.4.0.tgz", | ||||||
|  | |||||||
| @ -121,7 +121,6 @@ | |||||||
|     "@angular/language-service": "~10.0.0", |     "@angular/language-service": "~10.0.0", | ||||||
|     "@ionic/angular-toolkit": "^2.3.0", |     "@ionic/angular-toolkit": "^2.3.0", | ||||||
|     "@types/node": "^12.12.64", |     "@types/node": "^12.12.64", | ||||||
|     "codelyzer": "^6.0.0", |  | ||||||
|     "@typescript-eslint/eslint-plugin": "4.3.0", |     "@typescript-eslint/eslint-plugin": "4.3.0", | ||||||
|     "@typescript-eslint/parser": "4.3.0", |     "@typescript-eslint/parser": "4.3.0", | ||||||
|     "eslint": "^7.6.0", |     "eslint": "^7.6.0", | ||||||
| @ -131,6 +130,8 @@ | |||||||
|     "eslint-plugin-jest": "^24.1.0", |     "eslint-plugin-jest": "^24.1.0", | ||||||
|     "eslint-plugin-jsdoc": "^30.6.3", |     "eslint-plugin-jsdoc": "^30.6.3", | ||||||
|     "eslint-plugin-prefer-arrow": "^1.2.2", |     "eslint-plugin-prefer-arrow": "^1.2.2", | ||||||
|  |     "eslint-plugin-promise": "^4.2.1", | ||||||
|  |     "faker": "^5.1.0", | ||||||
|     "jest": "^26.5.0", |     "jest": "^26.5.0", | ||||||
|     "jest-preset-angular": "^8.3.1", |     "jest-preset-angular": "^8.3.1", | ||||||
|     "ts-jest": "^26.4.1", |     "ts-jest": "^26.4.1", | ||||||
|  | |||||||
							
								
								
									
										75
									
								
								src/app/classes/error.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								src/app/classes/error.test.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,75 @@ | |||||||
|  | // (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 Faker from 'faker'; | ||||||
|  | 
 | ||||||
|  | import { CoreError } from './error'; | ||||||
|  | 
 | ||||||
|  | describe('CoreError', () => { | ||||||
|  | 
 | ||||||
|  |     it('behaves like an error', () => { | ||||||
|  |         // Arrange
 | ||||||
|  |         const message = Faker.lorem.sentence(); | ||||||
|  | 
 | ||||||
|  |         let error: CoreError | null = null; | ||||||
|  | 
 | ||||||
|  |         // Act
 | ||||||
|  |         try { | ||||||
|  |             throw new CoreError(message); | ||||||
|  |         } catch (e) { | ||||||
|  |             error = e; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Assert
 | ||||||
|  |         expect(error).not.toBeNull(); | ||||||
|  |         expect(error).toBeInstanceOf(Error); | ||||||
|  |         expect(error).toBeInstanceOf(CoreError); | ||||||
|  |         expect(error!.name).toEqual('CoreError'); | ||||||
|  |         expect(error!.message).toEqual(message); | ||||||
|  |         expect(error!.stack).not.toBeNull(); | ||||||
|  |         expect(error!.stack).toContain('src/app/classes/error.test.ts'); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('can be subclassed', () => { | ||||||
|  |         // Arrange
 | ||||||
|  |         class CustomCoreError extends CoreError { | ||||||
|  | 
 | ||||||
|  |             constructor(m: string) { | ||||||
|  |                 super(`Custom message: ${m}`); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const message = Faker.lorem.sentence(); | ||||||
|  | 
 | ||||||
|  |         let error: CustomCoreError | null = null; | ||||||
|  | 
 | ||||||
|  |         // Act
 | ||||||
|  |         try { | ||||||
|  |             throw new CustomCoreError(message); | ||||||
|  |         } catch (e) { | ||||||
|  |             error = e; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Assert
 | ||||||
|  |         expect(error).not.toBeNull(); | ||||||
|  |         expect(error).toBeInstanceOf(Error); | ||||||
|  |         expect(error).toBeInstanceOf(CoreError); | ||||||
|  |         expect(error).toBeInstanceOf(CustomCoreError); | ||||||
|  |         expect(error!.name).toEqual('CustomCoreError'); | ||||||
|  |         expect(error!.message).toEqual(`Custom message: ${message}`); | ||||||
|  |         expect(error!.stack).not.toBeNull(); | ||||||
|  |         expect(error!.stack).toContain('src/app/classes/error.test.ts'); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  | }); | ||||||
| @ -13,9 +13,10 @@ | |||||||
| // limitations under the License.
 | // limitations under the License.
 | ||||||
| 
 | 
 | ||||||
| import { Injectable } from '@angular/core'; | import { Injectable } from '@angular/core'; | ||||||
|  | import { NavController } from '@ionic/angular'; | ||||||
| import { WKUserScriptWindow, WKUserScriptInjectionTime } from 'cordova-plugin-wkuserscript'; | import { WKUserScriptWindow, WKUserScriptInjectionTime } from 'cordova-plugin-wkuserscript'; | ||||||
| 
 | 
 | ||||||
| import { CoreApp, CoreAppProvider } from '@services/app'; | import { CoreApp } from '@services/app'; | ||||||
| import { CoreFile } from '@services/file'; | import { CoreFile } from '@services/file'; | ||||||
| import { CoreFileHelper } from '@services/file-helper'; | import { CoreFileHelper } from '@services/file-helper'; | ||||||
| import { CoreSites } from '@services/sites'; | import { CoreSites } from '@services/sites'; | ||||||
| @ -24,42 +25,35 @@ import { CoreTextUtils } from '@services/utils/text'; | |||||||
| import { CoreUrlUtils } from '@services/utils/url'; | import { CoreUrlUtils } from '@services/utils/url'; | ||||||
| import { CoreUtils } from '@services/utils/utils'; | import { CoreUtils } from '@services/utils/utils'; | ||||||
| 
 | 
 | ||||||
| import { makeSingleton, Translate, Network, Platform, NgZone } from '@singletons/core.singletons'; | import { makeSingleton, Network, Platform, NgZone } from '@singletons/core.singletons'; | ||||||
| import { CoreLogger } from '@singletons/logger'; | import { CoreLogger } from '@singletons/logger'; | ||||||
| import { CoreUrl } from '@singletons/url'; | import { CoreUrl } from '@singletons/url'; | ||||||
| import { CoreWindow } from '@singletons/window'; | import { CoreWindow } from '@singletons/window'; | ||||||
| 
 | 
 | ||||||
|  | /** | ||||||
|  |  * Possible types of frame elements. | ||||||
|  |  */ | ||||||
|  | type CoreFrameElement = (HTMLIFrameElement | HTMLFrameElement | HTMLObjectElement | HTMLEmbedElement) & { | ||||||
|  |     window?: Window; | ||||||
|  |     getWindow?(): Window; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| /* | /* | ||||||
|  * "Utils" service with helper functions for iframes, embed and similar. |  * "Utils" service with helper functions for iframes, embed and similar. | ||||||
|  */ |  */ | ||||||
| @Injectable() | @Injectable() | ||||||
| export class CoreIframeUtilsProvider { | export class CoreIframeUtilsProvider { | ||||||
|     static FRAME_TAGS = ['iframe', 'frame', 'object', 'embed']; | 
 | ||||||
|  |     static readonly FRAME_TAGS = ['iframe', 'frame', 'object', 'embed']; | ||||||
| 
 | 
 | ||||||
|     protected logger: CoreLogger; |     protected logger: CoreLogger; | ||||||
| 
 | 
 | ||||||
|     constructor() { |     constructor() { | ||||||
|         this.logger = CoreLogger.getInstance('CoreUtilsProvider'); |         this.logger = CoreLogger.getInstance('CoreUtilsProvider'); | ||||||
| 
 | 
 | ||||||
|         const win = <WKUserScriptWindow> window; |         if (CoreApp.instance.isIOS() && 'WKUserScript' in window) { | ||||||
| 
 |             // eslint-disable-next-line promise/catch-or-return
 | ||||||
|         if (CoreApp.instance.isIOS() && win.WKUserScript) { |             Platform.instance.ready().then(() => this.injectiOSScripts(window)); | ||||||
|             Platform.instance.ready().then(() => { |  | ||||||
|                 // Inject code to the iframes because we cannot access the online ones.
 |  | ||||||
|                 const wwwPath = CoreFile.instance.getWWWAbsolutePath(); |  | ||||||
|                 const linksPath = CoreTextUtils.instance.concatenatePaths(wwwPath, 'assets/js/iframe-treat-links.js'); |  | ||||||
|                 const recaptchaPath = CoreTextUtils.instance.concatenatePaths(wwwPath, 'assets/js/iframe-recaptcha.js'); |  | ||||||
| 
 |  | ||||||
|                 win.WKUserScript.addScript({id: 'CoreIframeUtilsLinksScript', file: linksPath}); |  | ||||||
|                 win.WKUserScript.addScript({ |  | ||||||
|                     id: 'CoreIframeUtilsRecaptchaScript', |  | ||||||
|                     file: recaptchaPath, |  | ||||||
|                     injectionTime: WKUserScriptInjectionTime.END, |  | ||||||
|                 }); |  | ||||||
| 
 |  | ||||||
|                 // Handle post messages received by iframes.
 |  | ||||||
|                 window.addEventListener('message', this.handleIframeMessage.bind(this)); |  | ||||||
|             }); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -70,8 +64,8 @@ export class CoreIframeUtilsProvider { | |||||||
|      * @param isSubframe Whether it's a frame inside another frame. |      * @param isSubframe Whether it's a frame inside another frame. | ||||||
|      * @return True if frame is online and the app is offline, false otherwise. |      * @return True if frame is online and the app is offline, false otherwise. | ||||||
|      */ |      */ | ||||||
|     checkOnlineFrameInOffline(element: any, isSubframe?: boolean): boolean { |     checkOnlineFrameInOffline(element: CoreFrameElement, isSubframe?: boolean): boolean { | ||||||
|         const src = element.src || element.data; |         const src = 'src' in element ? element.src : element.data; | ||||||
| 
 | 
 | ||||||
|         if (src && src != 'about:blank' && !CoreUrlUtils.instance.isLocalFileUrl(src) && !CoreApp.instance.isOnline()) { |         if (src && src != 'about:blank' && !CoreUrlUtils.instance.isLocalFileUrl(src) && !CoreApp.instance.isOnline()) { | ||||||
|             if (element.classList.contains('core-iframe-offline-disabled')) { |             if (element.classList.contains('core-iframe-offline-disabled')) { | ||||||
| @ -86,8 +80,6 @@ export class CoreIframeUtilsProvider { | |||||||
|             div.setAttribute('padding', ''); |             div.setAttribute('padding', ''); | ||||||
|             div.classList.add('core-iframe-offline-warning'); |             div.classList.add('core-iframe-offline-warning'); | ||||||
| 
 | 
 | ||||||
|             const site = CoreSites.instance.getCurrentSite(); |  | ||||||
|             const username = site ? site.getInfo().username : undefined; |  | ||||||
|             // @todo Handle link
 |             // @todo Handle link
 | ||||||
| 
 | 
 | ||||||
|             // Add a class to specify that the iframe is hidden.
 |             // Add a class to specify that the iframe is hidden.
 | ||||||
| @ -112,8 +104,13 @@ export class CoreIframeUtilsProvider { | |||||||
|             return true; |             return true; | ||||||
|         } else if (element.classList.contains('core-iframe-offline-disabled')) { |         } else if (element.classList.contains('core-iframe-offline-disabled')) { | ||||||
|             // Reload the frame.
 |             // Reload the frame.
 | ||||||
|  |             if ('src' in element) { | ||||||
|  |                 // eslint-disable-next-line no-self-assign
 | ||||||
|                 element.src = element.src; |                 element.src = element.src; | ||||||
|  |             } else { | ||||||
|  |                 // eslint-disable-next-line no-self-assign
 | ||||||
|                 element.data = element.data; |                 element.data = element.data; | ||||||
|  |             } | ||||||
| 
 | 
 | ||||||
|             // Remove the warning and show the iframe
 |             // Remove the warning and show the iframe
 | ||||||
|             CoreDomUtils.instance.removeElement(element.parentElement, 'div.core-iframe-offline-warning'); |             CoreDomUtils.instance.removeElement(element.parentElement, 'div.core-iframe-offline-warning'); | ||||||
| @ -134,12 +131,14 @@ export class CoreIframeUtilsProvider { | |||||||
|      * @param element Element to treat (iframe, embed, ...). |      * @param element Element to treat (iframe, embed, ...). | ||||||
|      * @return Window and Document. |      * @return Window and Document. | ||||||
|      */ |      */ | ||||||
|     getContentWindowAndDocument(element: any): { window: Window, document: Document } { |     getContentWindowAndDocument(element: CoreFrameElement): { window: Window; document: Document } { | ||||||
|         let contentWindow: Window = element.contentWindow; |         let contentWindow: Window = 'contentWindow' in element ? element.contentWindow : undefined; | ||||||
|         let contentDocument: Document; |         let contentDocument: Document; | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             contentDocument = element.contentDocument || (contentWindow && contentWindow.document); |             contentDocument = 'contentDocument' in element && element.contentDocument | ||||||
|  |                 ? element.contentDocument | ||||||
|  |                 : contentWindow && contentWindow.document; | ||||||
|         } catch (ex) { |         } catch (ex) { | ||||||
|             // Ignore errors.
 |             // Ignore errors.
 | ||||||
|         } |         } | ||||||
| @ -149,7 +148,7 @@ export class CoreIframeUtilsProvider { | |||||||
|             contentWindow = contentDocument.defaultView; |             contentWindow = contentDocument.defaultView; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (!contentWindow && element.getSVGDocument) { |         if (!contentWindow && 'getSVGDocument' in element) { | ||||||
|             // It's probably an <embed>. Try to get the window and the document.
 |             // It's probably an <embed>. Try to get the window and the document.
 | ||||||
|             try { |             try { | ||||||
|                 contentDocument = element.getSVGDocument(); |                 contentDocument = element.getSVGDocument(); | ||||||
| @ -187,9 +186,6 @@ export class CoreIframeUtilsProvider { | |||||||
|             case 'link_clicked': |             case 'link_clicked': | ||||||
|                 this.linkClicked(event.data.link); |                 this.linkClicked(event.data.link); | ||||||
|                 break; |                 break; | ||||||
| 
 |  | ||||||
|             default: |  | ||||||
|                 break; |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -202,10 +198,15 @@ export class CoreIframeUtilsProvider { | |||||||
|      * @param contentDocument The document of the element contents. |      * @param contentDocument The document of the element contents. | ||||||
|      * @param navCtrl NavController to use if a link can be opened in the app. |      * @param navCtrl NavController to use if a link can be opened in the app. | ||||||
|      */ |      */ | ||||||
|     redefineWindowOpen(element: any, contentWindow: Window, contentDocument: Document, navCtrl?: any): void { |     redefineWindowOpen( | ||||||
|  |         element: CoreFrameElement, | ||||||
|  |         contentWindow: Window, | ||||||
|  |         contentDocument: Document, | ||||||
|  |         navCtrl?: NavController, | ||||||
|  |     ): void { | ||||||
|         if (contentWindow) { |         if (contentWindow) { | ||||||
|             // Intercept window.open.
 |             // Intercept window.open.
 | ||||||
|             (<any> contentWindow).open = (url: string, name: string): Window => { |             contentWindow.open = (url: string, name: string) => { | ||||||
|                 this.windowOpen(url, name, element, navCtrl); |                 this.windowOpen(url, name, element, navCtrl); | ||||||
| 
 | 
 | ||||||
|                 return null; |                 return null; | ||||||
| @ -216,7 +217,7 @@ export class CoreIframeUtilsProvider { | |||||||
|             // Search sub frames.
 |             // Search sub frames.
 | ||||||
|             CoreIframeUtilsProvider.FRAME_TAGS.forEach((tag) => { |             CoreIframeUtilsProvider.FRAME_TAGS.forEach((tag) => { | ||||||
|                 const elements = Array.from(contentDocument.querySelectorAll(tag)); |                 const elements = Array.from(contentDocument.querySelectorAll(tag)); | ||||||
|                 elements.forEach((subElement) => { |                 elements.forEach((subElement: CoreFrameElement) => { | ||||||
|                     this.treatFrame(subElement, true, navCtrl); |                     this.treatFrame(subElement, true, navCtrl); | ||||||
|                 }); |                 }); | ||||||
|             }); |             }); | ||||||
| @ -231,7 +232,7 @@ export class CoreIframeUtilsProvider { | |||||||
|      * @param isSubframe Whether it's a frame inside another frame. |      * @param isSubframe Whether it's a frame inside another frame. | ||||||
|      * @param navCtrl NavController to use if a link can be opened in the app. |      * @param navCtrl NavController to use if a link can be opened in the app. | ||||||
|      */ |      */ | ||||||
|     treatFrame(element: any, isSubframe?: boolean, navCtrl?: any): void { |     treatFrame(element: CoreFrameElement, isSubframe?: boolean, navCtrl?: NavController): void { | ||||||
|         if (element) { |         if (element) { | ||||||
|             this.checkOnlineFrameInOffline(element, isSubframe); |             this.checkOnlineFrameInOffline(element, isSubframe); | ||||||
| 
 | 
 | ||||||
| @ -266,7 +267,7 @@ export class CoreIframeUtilsProvider { | |||||||
|      * @param element Element to treat (iframe, embed, ...). |      * @param element Element to treat (iframe, embed, ...). | ||||||
|      * @param contentDocument The document of the element contents. |      * @param contentDocument The document of the element contents. | ||||||
|      */ |      */ | ||||||
|     treatFrameLinks(element: any, contentDocument: Document): void { |     treatFrameLinks(element: CoreFrameElement, contentDocument: Document): void { | ||||||
|         if (!contentDocument) { |         if (!contentDocument) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| @ -292,7 +293,7 @@ export class CoreIframeUtilsProvider { | |||||||
|             link.treated = true; |             link.treated = true; | ||||||
|             link.addEventListener('click', this.linkClicked.bind(this, link, element)); |             link.addEventListener('click', this.linkClicked.bind(this, link, element)); | ||||||
|         }, { |         }, { | ||||||
|             capture: true // Use capture to fix this listener not called if the element clicked is too deep in the DOM.
 |             capture: true, // Use capture to fix this listener not called if the element clicked is too deep in the DOM.
 | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -305,11 +306,13 @@ export class CoreIframeUtilsProvider { | |||||||
|      * @param navCtrl NavController to use if a link can be opened in the app. |      * @param navCtrl NavController to use if a link can be opened in the app. | ||||||
|      * @return Promise resolved when done. |      * @return Promise resolved when done. | ||||||
|      */ |      */ | ||||||
|     protected async windowOpen(url: string, name: string, element?: any, navCtrl?: any): Promise<void> { |     protected async windowOpen(url: string, name: string, element?: CoreFrameElement, navCtrl?: NavController): Promise<void> { | ||||||
|         const scheme = CoreUrlUtils.instance.getUrlScheme(url); |         const scheme = CoreUrlUtils.instance.getUrlScheme(url); | ||||||
|         if (!scheme) { |         if (!scheme) { | ||||||
|             // It's a relative URL, use the frame src to create the full URL.
 |             // It's a relative URL, use the frame src to create the full URL.
 | ||||||
|             const src = element && (element.src || element.data); |             const src = element | ||||||
|  |                 ? ('src' in element ? element.src : element.data) | ||||||
|  |                 : null; | ||||||
|             if (src) { |             if (src) { | ||||||
|                 const dirAndFile = CoreFile.instance.getFileAndDirectoryFromPath(src); |                 const dirAndFile = CoreFile.instance.getFileAndDirectoryFromPath(src); | ||||||
|                 if (dirAndFile.directory) { |                 if (dirAndFile.directory) { | ||||||
| @ -372,8 +375,11 @@ export class CoreIframeUtilsProvider { | |||||||
|      * @param event Click event. |      * @param event Click event. | ||||||
|      * @return Promise resolved when done. |      * @return Promise resolved when done. | ||||||
|      */ |      */ | ||||||
|     protected async linkClicked(link: {href: string, target?: string}, element?: HTMLFrameElement | HTMLObjectElement, |     protected async linkClicked( | ||||||
|             event?: Event): Promise<void> { |         link: {href: string; target?: string}, | ||||||
|  |         element?: HTMLFrameElement | HTMLObjectElement, | ||||||
|  |         event?: Event, | ||||||
|  |     ): Promise<void> { | ||||||
|         if (event && event.defaultPrevented) { |         if (event && event.defaultPrevented) { | ||||||
|             // Event already prevented by some other code.
 |             // Event already prevented by some other code.
 | ||||||
|             return; |             return; | ||||||
| @ -438,6 +444,28 @@ export class CoreIframeUtilsProvider { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Inject code to the iframes because we cannot access the online ones. | ||||||
|  |      * | ||||||
|  |      * @param userScriptWindow Window. | ||||||
|  |      */ | ||||||
|  |     private injectiOSScripts(userScriptWindow: WKUserScriptWindow) { | ||||||
|  |         const wwwPath = CoreFile.instance.getWWWAbsolutePath(); | ||||||
|  |         const linksPath = CoreTextUtils.instance.concatenatePaths(wwwPath, 'assets/js/iframe-treat-links.js'); | ||||||
|  |         const recaptchaPath = CoreTextUtils.instance.concatenatePaths(wwwPath, 'assets/js/iframe-recaptcha.js'); | ||||||
|  | 
 | ||||||
|  |         userScriptWindow.WKUserScript.addScript({ id: 'CoreIframeUtilsLinksScript', file: linksPath }); | ||||||
|  |         userScriptWindow.WKUserScript.addScript({ | ||||||
|  |             id: 'CoreIframeUtilsRecaptchaScript', | ||||||
|  |             file: recaptchaPath, | ||||||
|  |             injectionTime: WKUserScriptInjectionTime.END, | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         // Handle post messages received by iframes.
 | ||||||
|  |         window.addEventListener('message', this.handleIframeMessage.bind(this)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export class CoreIframeUtils extends makeSingleton(CoreIframeUtilsProvider) {} | export class CoreIframeUtils extends makeSingleton(CoreIframeUtilsProvider) {} | ||||||
|  | |||||||
| @ -13,36 +13,55 @@ | |||||||
| // limitations under the License.
 | // limitations under the License.
 | ||||||
| 
 | 
 | ||||||
| import { Injectable } from '@angular/core'; | import { Injectable } from '@angular/core'; | ||||||
|  | import { FileEntry } from '@ionic-native/file'; | ||||||
| 
 | 
 | ||||||
| import { CoreFile } from '@services/file'; | import { CoreFile } from '@services/file'; | ||||||
| import { CoreTextUtils } from '@services/utils/text'; | import { CoreTextUtils } from '@services/utils/text'; | ||||||
| import { makeSingleton, Translate, Http } from '@singletons/core.singletons'; | import { makeSingleton, Translate, Http } from '@singletons/core.singletons'; | ||||||
| import { CoreLogger } from '@singletons/logger'; | import { CoreLogger } from '@singletons/logger'; | ||||||
|  | import { CoreWSExternalFile } from '@services/ws'; | ||||||
|  | import { CoreUtils } from '@services/utils/utils'; | ||||||
|  | 
 | ||||||
|  | interface MimeTypeInfo { | ||||||
|  |     type: string; | ||||||
|  |     icon?: string; | ||||||
|  |     groups?: string[]; | ||||||
|  | 
 | ||||||
|  |     // eslint-disable-next-line id-blacklist
 | ||||||
|  |     string?: string; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface MimeTypeGroupInfo { | ||||||
|  |     mimetypes: string[]; | ||||||
|  |     extensions: string[]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const EXTENSION_REGEX = /^[a-z0-9]+$/; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  * "Utils" service with helper functions for mimetypes and extensions. |  * "Utils" service with helper functions for mimetypes and extensions. | ||||||
|  */ |  */ | ||||||
| @Injectable() | @Injectable() | ||||||
| export class CoreMimetypeUtilsProvider { | export class CoreMimetypeUtilsProvider { | ||||||
|  | 
 | ||||||
|     protected logger: CoreLogger; |     protected logger: CoreLogger; | ||||||
|     protected extToMime = {}; // Object to map extensions -> mimetypes.
 |     protected extToMime: Record<string, MimeTypeInfo> = {}; | ||||||
|     protected mimeToExt = {}; // Object to map mimetypes -> extensions.
 |     protected mimeToExt: Record<string, string[]> = {}; | ||||||
|     protected groupsMimeInfo = {}; // Object to hold extensions and mimetypes that belong to a certain "group" (audio, video, ...).
 |     protected groupsMimeInfo: Record<string, MimeTypeGroupInfo> = {}; | ||||||
|     protected extensionRegex = /^[a-z0-9]+$/; |  | ||||||
| 
 | 
 | ||||||
|     constructor() { |     constructor() { | ||||||
|         this.logger = CoreLogger.getInstance('CoreMimetypeUtilsProvider'); |         this.logger = CoreLogger.getInstance('CoreMimetypeUtilsProvider'); | ||||||
| 
 | 
 | ||||||
|         Http.instance.get('assets/exttomime.json').subscribe((result) => { |         Http.instance.get('assets/exttomime.json').subscribe((result: Record<string, MimeTypeInfo>) => { | ||||||
|             this.extToMime = result; |             this.extToMime = result; | ||||||
|         }, (err) => { |         }, () => { | ||||||
|             // Error, shouldn't happen.
 |             // Error, shouldn't happen.
 | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         Http.instance.get('assets/mimetoext.json').subscribe((result) => { |         Http.instance.get('assets/mimetoext.json').subscribe((result: Record<string, string[]>) => { | ||||||
|             this.mimeToExt = result; |             this.mimeToExt = result; | ||||||
|         }, (err) => { |         }, () => { | ||||||
|             // Error, shouldn't happen.
 |             // Error, shouldn't happen
 | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -148,31 +167,38 @@ export class CoreMimetypeUtilsProvider { | |||||||
|      * Set the embed type to display an embedded file and mimetype if not found. |      * Set the embed type to display an embedded file and mimetype if not found. | ||||||
|      * |      * | ||||||
|      * @param file File object. |      * @param file File object. | ||||||
|      * @paran path Alternative path that will override fileurl from file object. |      * @param path Alternative path that will override fileurl from file object. | ||||||
|      */ |      */ | ||||||
|     getEmbeddedHtml(file: any, path?: string): string { |     getEmbeddedHtml(file: CoreWSExternalFile | FileEntry, path?: string): string { | ||||||
|         let ext; |         const filename = CoreUtils.instance.isFileEntry(file) ? (file as FileEntry).name : file.filename; | ||||||
|         const filename = file.filename || file.name; |         const extension = !CoreUtils.instance.isFileEntry(file) && file.mimetype | ||||||
|  |             ? this.getExtension(file.mimetype) | ||||||
|  |             : this.getFileExtension(filename); | ||||||
|  |         const mimeType = !CoreUtils.instance.isFileEntry(file) && file.mimetype ? file.mimetype : this.getMimeType(extension); | ||||||
| 
 | 
 | ||||||
|         if (file.mimetype) { |         // @todo linting: See if this can be removed
 | ||||||
|             ext = this.getExtension(file.mimetype); |         (file as CoreWSExternalFile).mimetype = mimeType; | ||||||
|         } else { |  | ||||||
|             ext = this.getFileExtension(filename); |  | ||||||
|             file.mimetype = this.getMimeType(ext); |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         if (this.canBeEmbedded(ext)) { |         if (this.canBeEmbedded(extension)) { | ||||||
|             file.embedType = this.getExtensionType(ext); |             const embedType = this.getExtensionType(extension); | ||||||
| 
 | 
 | ||||||
|             path = CoreFile.instance.convertFileSrc(path || file.fileurl || file.url || (file.toURL && file.toURL())); |             // @todo linting: See if this can be removed
 | ||||||
|  |             (file as { embedType: string }).embedType = embedType; | ||||||
| 
 | 
 | ||||||
|             if (file.embedType == 'image') { |             path = CoreFile.instance.convertFileSrc(path ?? (CoreUtils.instance.isFileEntry(file) ? file.toURL() : file.fileurl)); | ||||||
|                 return '<img src="' + path + '">'; | 
 | ||||||
|             } |             switch (embedType) { | ||||||
|             if (file.embedType == 'audio' || file.embedType == 'video') { |                 case 'image': | ||||||
|                 return '<' + file.embedType + ' controls title="' + filename + '" src="' + path + '">' + |                     return `<img src="${path}">`; | ||||||
|                     '<source src="' + path + '" type="' + file.mimetype + '">' + |                 case 'audio': | ||||||
|                     '</' + file.embedType + '>'; |                 case 'video': | ||||||
|  |                     return [ | ||||||
|  |                         `<${embedType} controls title="${filename}" src="${path}">`, | ||||||
|  |                         `<source src="${path}" type="${mimeType}">`, | ||||||
|  |                         `</${embedType}>`, | ||||||
|  |                     ].join(''); | ||||||
|  |                 default: | ||||||
|  |                     return ''; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -290,7 +316,7 @@ export class CoreMimetypeUtilsProvider { | |||||||
|                 candidate = candidate.substr(0, position); |                 candidate = candidate.substr(0, position); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             if (this.extensionRegex.test(candidate)) { |             if (EXTENSION_REGEX.test(candidate)) { | ||||||
|                 extension = candidate; |                 extension = candidate; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @ -338,7 +364,7 @@ export class CoreMimetypeUtilsProvider { | |||||||
|      * @param field The field to get. If not supplied, all the info will be returned. |      * @param field The field to get. If not supplied, all the info will be returned. | ||||||
|      * @return Info for the group. |      * @return Info for the group. | ||||||
|      */ |      */ | ||||||
|     getGroupMimeInfo(group: string, field?: string): any { |     getGroupMimeInfo(group: string, field?: string): MimeTypeGroupInfo { | ||||||
|         if (typeof this.groupsMimeInfo[group] == 'undefined') { |         if (typeof this.groupsMimeInfo[group] == 'undefined') { | ||||||
|             this.fillGroupMimeInfo(group); |             this.fillGroupMimeInfo(group); | ||||||
|         } |         } | ||||||
| @ -372,13 +398,13 @@ export class CoreMimetypeUtilsProvider { | |||||||
|      * @param capitalise If true, capitalises first character of result. |      * @param capitalise If true, capitalises first character of result. | ||||||
|      * @return Type description. |      * @return Type description. | ||||||
|      */ |      */ | ||||||
|     getMimetypeDescription(obj: any, capitalise?: boolean): string { |     getMimetypeDescription(obj: FileEntry | { filename: string; mimetype: string } | string, capitalise?: boolean): string { | ||||||
|         const langPrefix = 'assets.mimetypes.'; |         const langPrefix = 'assets.mimetypes.'; | ||||||
|         let filename = ''; |         let filename = ''; | ||||||
|         let mimetype = ''; |         let mimetype = ''; | ||||||
|         let extension = ''; |         let extension = ''; | ||||||
| 
 | 
 | ||||||
|         if (typeof obj == 'object' && typeof obj.file == 'function') { |         if (typeof obj == 'object' && CoreUtils.instance.isFileEntry(obj)) { | ||||||
|             // It's a FileEntry. Don't use the file function because it's asynchronous and the type isn't reliable.
 |             // It's a FileEntry. Don't use the file function because it's asynchronous and the type isn't reliable.
 | ||||||
|             filename = obj.name; |             filename = obj.name; | ||||||
|         } else if (typeof obj == 'object') { |         } else if (typeof obj == 'object') { | ||||||
| @ -548,6 +574,7 @@ export class CoreMimetypeUtilsProvider { | |||||||
| 
 | 
 | ||||||
|         return path; |         return path; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export class CoreMimetypeUtils extends makeSingleton(CoreMimetypeUtilsProvider) {} | export class CoreMimetypeUtils extends makeSingleton(CoreMimetypeUtilsProvider) {} | ||||||
|  | |||||||
| @ -18,6 +18,8 @@ import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; | |||||||
| import { CoreApp } from '@services/app'; | import { CoreApp } from '@services/app'; | ||||||
| import { CoreLang } from '@services/lang'; | import { CoreLang } from '@services/lang'; | ||||||
| import { makeSingleton, Translate } from '@singletons/core.singletons'; | import { makeSingleton, Translate } from '@singletons/core.singletons'; | ||||||
|  | import { CoreWSExternalFile } from '@services/ws'; | ||||||
|  | import { Locutus } from '@singletons/locutus'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Different type of errors the app can treat. |  * Different type of errors the app can treat. | ||||||
| @ -36,7 +38,7 @@ export type CoreTextErrorObject = { | |||||||
| export class CoreTextUtilsProvider { | export class CoreTextUtilsProvider { | ||||||
| 
 | 
 | ||||||
|     // List of regular expressions to convert the old nomenclature to new nomenclature for disabled features.
 |     // List of regular expressions to convert the old nomenclature to new nomenclature for disabled features.
 | ||||||
|     protected DISABLED_FEATURES_COMPAT_REGEXPS = [ |     protected readonly DISABLED_FEATURES_COMPAT_REGEXPS: { old: RegExp; new: string }[] = [ | ||||||
|         { old: /\$mmLoginEmailSignup/g, new: 'CoreLoginEmailSignup' }, |         { old: /\$mmLoginEmailSignup/g, new: 'CoreLoginEmailSignup' }, | ||||||
|         { old: /\$mmSideMenuDelegate/g, new: 'CoreMainMenuDelegate' }, |         { old: /\$mmSideMenuDelegate/g, new: 'CoreMainMenuDelegate' }, | ||||||
|         { old: /\$mmCoursesDelegate/g, new: 'CoreCourseOptionsDelegate' }, |         { old: /\$mmCoursesDelegate/g, new: 'CoreCourseOptionsDelegate' }, | ||||||
| @ -82,7 +84,7 @@ export class CoreTextUtilsProvider { | |||||||
|         { old: /remoteAddOn_/g, new: 'sitePlugin_' }, |         { old: /remoteAddOn_/g, new: 'sitePlugin_' }, | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     protected template = document.createElement('template'); // A template element to convert HTML to element.
 |     protected template: HTMLTemplateElement = document.createElement('template'); // A template element to convert HTML to element.
 | ||||||
| 
 | 
 | ||||||
|     constructor(private sanitizer: DomSanitizer) { } |     constructor(private sanitizer: DomSanitizer) { } | ||||||
| 
 | 
 | ||||||
| @ -200,7 +202,6 @@ export class CoreTextUtilsProvider { | |||||||
|      * @return Size in human readable format. |      * @return Size in human readable format. | ||||||
|      */ |      */ | ||||||
|     bytesToSize(bytes: number, precision: number = 2): string { |     bytesToSize(bytes: number, precision: number = 2): string { | ||||||
| 
 |  | ||||||
|         if (typeof bytes == 'undefined' || bytes === null || bytes < 0) { |         if (typeof bytes == 'undefined' || bytes === null || bytes < 0) { | ||||||
|             return Translate.instance.instant('core.notapplicable'); |             return Translate.instance.instant('core.notapplicable'); | ||||||
|         } |         } | ||||||
| @ -449,9 +450,17 @@ export class CoreTextUtilsProvider { | |||||||
|      * @param courseId Course ID the text belongs to. It can be used to improve performance with filters. |      * @param courseId Course ID the text belongs to. It can be used to improve performance with filters. | ||||||
|      * @deprecated since 3.8.3. Please use viewText instead. |      * @deprecated since 3.8.3. Please use viewText instead. | ||||||
|      */ |      */ | ||||||
|     expandText(title: string, text: string, component?: string, componentId?: string | number, files?: any[], |     expandText( | ||||||
|             filter?: boolean, contextLevel?: string, instanceId?: number, courseId?: number): void { |         title: string, | ||||||
| 
 |         text: string, | ||||||
|  |         component?: string, | ||||||
|  |         componentId?: string | number, | ||||||
|  |         files?: CoreWSExternalFile[], | ||||||
|  |         filter?: boolean, | ||||||
|  |         contextLevel?: string, | ||||||
|  |         instanceId?: number, | ||||||
|  |         courseId?: number, | ||||||
|  |     ): void { | ||||||
|         return this.viewText(title, text, { |         return this.viewText(title, text, { | ||||||
|             component, |             component, | ||||||
|             componentId, |             componentId, | ||||||
| @ -531,12 +540,12 @@ export class CoreTextUtilsProvider { | |||||||
|      * @param files Files to extract the URL from. They need to have the URL in a 'url' or 'fileurl' attribute. |      * @param files Files to extract the URL from. They need to have the URL in a 'url' or 'fileurl' attribute. | ||||||
|      * @return Pluginfile URL, undefined if no files found. |      * @return Pluginfile URL, undefined if no files found. | ||||||
|      */ |      */ | ||||||
|     getTextPluginfileUrl(files: any[]): string { |     getTextPluginfileUrl(files: CoreWSExternalFile[]): string { | ||||||
|         if (files && files.length) { |         if (files && files.length) { | ||||||
|             const fileURL = files[0].url || files[0].fileurl; |             const url = files[0].fileurl; | ||||||
| 
 | 
 | ||||||
|             // Remove text after last slash (encoded or not).
 |             // Remove text after last slash (encoded or not).
 | ||||||
|             return fileURL.substr(0, Math.max(fileURL.lastIndexOf('/'), fileURL.lastIndexOf('%2F'))); |             return url.substr(0, Math.max(url.lastIndexOf('/'), url.lastIndexOf('%2F'))); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return undefined; |         return undefined; | ||||||
| @ -610,13 +619,17 @@ export class CoreTextUtilsProvider { | |||||||
|      * @param data Object to be checked. |      * @param data Object to be checked. | ||||||
|      * @return If the data has any long Unicode char on it. |      * @return If the data has any long Unicode char on it. | ||||||
|      */ |      */ | ||||||
|     hasUnicodeData(data: object): boolean { |     hasUnicodeData(data: Record<string, unknown>): boolean { | ||||||
|         for (const el in data) { |         for (const el in data) { | ||||||
|             if (typeof data[el] == 'object') { |             if (typeof data[el] == 'object') { | ||||||
|                 if (this.hasUnicodeData(data[el])) { |                 if (this.hasUnicodeData(data[el] as Record<string, unknown>)) { | ||||||
|                     return true; |                     return true; | ||||||
|                 } |                 } | ||||||
|             } else if (typeof data[el] == 'string' && this.hasUnicode(data[el])) { | 
 | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (typeof data[el] == 'string' && this.hasUnicode(data[el] as string)) { | ||||||
|                 return true; |                 return true; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @ -632,13 +645,13 @@ export class CoreTextUtilsProvider { | |||||||
|      * @param logErrorFn An error to call with the exception to log the error. If not supplied, no error. |      * @param logErrorFn An error to call with the exception to log the error. If not supplied, no error. | ||||||
|      * @return JSON parsed as object or what it gets. |      * @return JSON parsed as object or what it gets. | ||||||
|      */ |      */ | ||||||
|     parseJSON(json: string, defaultValue?: any, logErrorFn?: (error?: any) => void): any { |     parseJSON<T>(json: string, defaultValue?: T, logErrorFn?: (error?: Error) => void): T | string { | ||||||
|         try { |         try { | ||||||
|             return JSON.parse(json); |             return JSON.parse(json); | ||||||
|         } catch (ex) { |         } catch (error) { | ||||||
|             // Error, log the error if needed.
 |             // Error, log the error if needed.
 | ||||||
|             if (logErrorFn) { |             if (logErrorFn) { | ||||||
|                 logErrorFn(ex); |                 logErrorFn(error); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -675,7 +688,7 @@ export class CoreTextUtilsProvider { | |||||||
|             return ''; |             return ''; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return text.replace(/[#:\/\?\\]+/g, '_'); |         return text.replace(/[#:/?\\]+/g, '_'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -700,7 +713,7 @@ export class CoreTextUtilsProvider { | |||||||
|      * @param files Files to extract the pluginfile URL from. They need to have the URL in a url or fileurl attribute. |      * @param files Files to extract the pluginfile URL from. They need to have the URL in a url or fileurl attribute. | ||||||
|      * @return Treated text. |      * @return Treated text. | ||||||
|      */ |      */ | ||||||
|     replacePluginfileUrls(text: string, files: any[]): string { |     replacePluginfileUrls(text: string, files: CoreWSExternalFile[]): string { | ||||||
|         if (text && typeof text == 'string') { |         if (text && typeof text == 'string') { | ||||||
|             const fileURL = this.getTextPluginfileUrl(files); |             const fileURL = this.getTextPluginfileUrl(files); | ||||||
|             if (fileURL) { |             if (fileURL) { | ||||||
| @ -718,7 +731,7 @@ export class CoreTextUtilsProvider { | |||||||
|      * @param files Files to extract the pluginfile URL from. They need to have the URL in a url or fileurl attribute. |      * @param files Files to extract the pluginfile URL from. They need to have the URL in a url or fileurl attribute. | ||||||
|      * @return Treated text. |      * @return Treated text. | ||||||
|      */ |      */ | ||||||
|     restorePluginfileUrls(text: string, files: any[]): string { |     restorePluginfileUrls(text: string, files: CoreWSExternalFile[]): string { | ||||||
|         if (text && typeof text == 'string') { |         if (text && typeof text == 'string') { | ||||||
|             const fileURL = this.getTextPluginfileUrl(files); |             const fileURL = this.getTextPluginfileUrl(files); | ||||||
|             if (fileURL) { |             if (fileURL) { | ||||||
| @ -804,7 +817,6 @@ export class CoreTextUtilsProvider { | |||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Replace text within a portion of a string. Equivalent to PHP's substr_replace. |      * Replace text within a portion of a string. Equivalent to PHP's substr_replace. | ||||||
|      * Credits to http://locutus.io/php/strings/substr_replace/
 |  | ||||||
|      * |      * | ||||||
|      * @param str The string to treat. |      * @param str The string to treat. | ||||||
|      * @param replace The value to put inside the string. |      * @param replace The value to put inside the string. | ||||||
| @ -814,22 +826,7 @@ export class CoreTextUtilsProvider { | |||||||
|      * @return Treated string. |      * @return Treated string. | ||||||
|      */ |      */ | ||||||
|     substrReplace(str: string, replace: string, start: number, length?: number): string { |     substrReplace(str: string, replace: string, start: number, length?: number): string { | ||||||
|         length = typeof length != 'undefined' ? length : str.length; |         return Locutus.substrReplace(str, replace, start, length); | ||||||
| 
 |  | ||||||
|         if (start < 0) { |  | ||||||
|             start = start + str.length; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if (length < 0) { |  | ||||||
|             length = length + str.length - start; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return [ |  | ||||||
|             str.slice(0, start), |  | ||||||
|             replace.substr(0, length), |  | ||||||
|             replace.slice(length), |  | ||||||
|             str.slice(start + length) |  | ||||||
|         ].join(''); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -867,14 +864,14 @@ export class CoreTextUtilsProvider { | |||||||
|         return CoreLang.instance.getCurrentLanguage().then((language) => { |         return CoreLang.instance.getCurrentLanguage().then((language) => { | ||||||
|             // Match the current language.
 |             // Match the current language.
 | ||||||
|             const anyLangRegEx = /<(?:lang|span)[^>]+lang="[a-zA-Z0-9_-]+"[^>]*>(.*?)<\/(?:lang|span)>/g; |             const anyLangRegEx = /<(?:lang|span)[^>]+lang="[a-zA-Z0-9_-]+"[^>]*>(.*?)<\/(?:lang|span)>/g; | ||||||
|             let currentLangRegEx = new RegExp('<(?:lang|span)[^>]+lang="' + language + '"[^>]*>(.*?)<\/(?:lang|span)>', 'g'); |             let currentLangRegEx = new RegExp('<(?:lang|span)[^>]+lang="' + language + '"[^>]*>(.*?)</(?:lang|span)>', 'g'); | ||||||
| 
 | 
 | ||||||
|             if (!text.match(currentLangRegEx)) { |             if (!text.match(currentLangRegEx)) { | ||||||
|                 // Current lang not found. Try to find the first language.
 |                 // Current lang not found. Try to find the first language.
 | ||||||
|                 const matches = text.match(anyLangRegEx); |                 const matches = text.match(anyLangRegEx); | ||||||
|                 if (matches && matches[0]) { |                 if (matches && matches[0]) { | ||||||
|                     language = matches[0].match(/lang="([a-zA-Z0-9_-]+)"/)[1]; |                     language = matches[0].match(/lang="([a-zA-Z0-9_-]+)"/)[1]; | ||||||
|                     currentLangRegEx = new RegExp('<(?:lang|span)[^>]+lang="' + language + '"[^>]*>(.*?)<\/(?:lang|span)>', 'g'); |                     currentLangRegEx = new RegExp('<(?:lang|span)[^>]+lang="' + language + '"[^>]*>(.*?)</(?:lang|span)>', 'g'); | ||||||
|                 } else { |                 } else { | ||||||
|                     // No multi-lang tag found, stop.
 |                     // No multi-lang tag found, stop.
 | ||||||
|                     return text; |                     return text; | ||||||
| @ -915,221 +912,12 @@ export class CoreTextUtilsProvider { | |||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Unserialize Array from PHP. |      * Unserialize Array from PHP. | ||||||
|      * Taken from: https://github.com/kvz/locutus/blob/master/src/php/var/unserialize.js
 |  | ||||||
|      * |      * | ||||||
|      * @param data String to unserialize. |      * @param data String to unserialize. | ||||||
|      * @param logErrorFn An error to call with the exception to log the error. If not supplied, no error. |  | ||||||
|      * @return Unserialized data. |      * @return Unserialized data. | ||||||
|      */ |      */ | ||||||
|     unserialize(data: string, logErrorFn?: (error?: string) => void): any { |     unserialize<T = unknown>(data: string): T { | ||||||
|         //  Discuss at: http://locutus.io/php/unserialize/
 |         return Locutus.unserialize<T>(data); | ||||||
|         // Original by: Arpad Ray (mailto:arpad@php.net)
 |  | ||||||
|         // Improved by: Pedro Tainha (http://www.pedrotainha.com)
 |  | ||||||
|         // Improved by: Kevin van Zonneveld (http://kvz.io)
 |  | ||||||
|         // Improved by: Kevin van Zonneveld (http://kvz.io)
 |  | ||||||
|         // Improved by: Chris
 |  | ||||||
|         // Improved by: James
 |  | ||||||
|         // Improved by: Le Torbi
 |  | ||||||
|         // Improved by: Eli Skeggs
 |  | ||||||
|         // Bugfixed by: dptr1988
 |  | ||||||
|         // Bugfixed by: Kevin van Zonneveld (http://kvz.io)
 |  | ||||||
|         // Bugfixed by: Brett Zamir (http://brett-zamir.me)
 |  | ||||||
|         // Bugfixed by: philippsimon (https://github.com/philippsimon/)
 |  | ||||||
|         //  Revised by: d3x
 |  | ||||||
|         //    Input by: Brett Zamir (http://brett-zamir.me)
 |  | ||||||
|         //    Input by: Martin (http://www.erlenwiese.de/)
 |  | ||||||
|         //    Input by: kilops
 |  | ||||||
|         //    Input by: Jaroslaw Czarniak
 |  | ||||||
|         //    Input by: lovasoa (https://github.com/lovasoa/)
 |  | ||||||
|         //      Note 1: We feel the main purpose of this function should be
 |  | ||||||
|         //      Note 1: to ease the transport of data between php & js
 |  | ||||||
|         //      Note 1: Aiming for PHP-compatibility, we have to translate objects to arrays
 |  | ||||||
|         //   Example 1: unserialize('a:3:{i:0;s:5:"Kevin";i:1;s:3:"van";i:2;s:9:"Zonneveld";}')
 |  | ||||||
|         //   Returns 1: ['Kevin', 'van', 'Zonneveld']
 |  | ||||||
|         //   Example 2: unserialize('a:2:{s:9:"firstName";s:5:"Kevin";s:7:"midName";s:3:"van";}')
 |  | ||||||
|         //   Returns 2: {firstName: 'Kevin', midName: 'van'}
 |  | ||||||
|         //   Example 3: unserialize('a:3:{s:2:"ü";s:2:"ü";s:3:"四";s:3:"四";s:4:"𠜎";s:4:"𠜎";}')
 |  | ||||||
|         //   Returns 3: {'ü': 'ü', '四': '四', '𠜎': '𠜎'}
 |  | ||||||
| 
 |  | ||||||
|         const utf8Overhead = (str: string): number => { |  | ||||||
|             let s = str.length; |  | ||||||
| 
 |  | ||||||
|             for (let i = str.length - 1; i >= 0; i--) { |  | ||||||
|                 const code = str.charCodeAt(i); |  | ||||||
|                 if (code > 0x7f && code <= 0x7ff) { |  | ||||||
|                     s++; |  | ||||||
|                 } else if (code > 0x7ff && code <= 0xffff) { |  | ||||||
|                     s += 2; |  | ||||||
|                 } |  | ||||||
|                 // Trail surrogate.
 |  | ||||||
|                 if (code >= 0xDC00 && code <= 0xDFFF) { |  | ||||||
|                     i--; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             return s - 1; |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         const error = (type: string, msg: string): void => { |  | ||||||
|             if (logErrorFn) { |  | ||||||
|                 logErrorFn(type + msg); |  | ||||||
|             } |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         const readUntil = (data: string, offset: number, stopchr: string): Array<any> => { |  | ||||||
|             let i = 2; |  | ||||||
|             const buf = []; |  | ||||||
|             let chr = data.slice(offset, offset + 1); |  | ||||||
| 
 |  | ||||||
|             while (chr !== stopchr) { |  | ||||||
|                 if ((i + offset) > data.length) { |  | ||||||
|                     error('Error', 'Invalid'); |  | ||||||
|                 } |  | ||||||
|                 buf.push(chr); |  | ||||||
|                 chr = data.slice(offset + (i - 1), offset + i); |  | ||||||
|                 i += 1; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             return [buf.length, buf.join('')]; |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         const readChrs = (data: string, offset: number, length: number): Array<any> => { |  | ||||||
|             let chr; |  | ||||||
|             const buf = []; |  | ||||||
| 
 |  | ||||||
|             for (let i = 0; i < length; i++) { |  | ||||||
|                 chr = data.slice(offset + (i - 1), offset + i); |  | ||||||
|                 buf.push(chr); |  | ||||||
|                 length -= utf8Overhead(chr); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             return [buf.length, buf.join('')]; |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         const _unserialize = (data: string, offset: number): any => { |  | ||||||
|             let dtype, |  | ||||||
|                 dataoffset, |  | ||||||
|                 keyandchrs, |  | ||||||
|                 keys, |  | ||||||
|                 contig, |  | ||||||
|                 length, |  | ||||||
|                 array, |  | ||||||
|                 readdata, |  | ||||||
|                 readData, |  | ||||||
|                 ccount, |  | ||||||
|                 stringlength, |  | ||||||
|                 i, |  | ||||||
|                 key, |  | ||||||
|                 kprops, |  | ||||||
|                 kchrs, |  | ||||||
|                 vprops, |  | ||||||
|                 vchrs, |  | ||||||
|                 value, |  | ||||||
|                 chrs = 0, |  | ||||||
|                 typeconvert = (x: any): any => { |  | ||||||
|                     return x; |  | ||||||
|                 }; |  | ||||||
| 
 |  | ||||||
|             if (!offset) { |  | ||||||
|                 offset = 0; |  | ||||||
|             } |  | ||||||
|             dtype = (data.slice(offset, offset + 1)).toLowerCase(); |  | ||||||
| 
 |  | ||||||
|             dataoffset = offset + 2; |  | ||||||
| 
 |  | ||||||
|             switch (dtype) { |  | ||||||
|                 case 'i': |  | ||||||
|                     typeconvert = (x: any): number => { |  | ||||||
|                         return parseInt(x, 10); |  | ||||||
|                     }; |  | ||||||
|                     readData = readUntil(data, dataoffset, ';'); |  | ||||||
|                     chrs = readData[0]; |  | ||||||
|                     readdata = readData[1]; |  | ||||||
|                     dataoffset += chrs + 1; |  | ||||||
|                     break; |  | ||||||
|                 case 'b': |  | ||||||
|                     typeconvert = (x: any): boolean => { |  | ||||||
|                         return parseInt(x, 10) !== 0; |  | ||||||
|                     }; |  | ||||||
|                     readData = readUntil(data, dataoffset, ';'); |  | ||||||
|                     chrs = readData[0]; |  | ||||||
|                     readdata = readData[1]; |  | ||||||
|                     dataoffset += chrs + 1; |  | ||||||
|                     break; |  | ||||||
|                 case 'd': |  | ||||||
|                     typeconvert = (x: any): number => { |  | ||||||
|                         return parseFloat(x); |  | ||||||
|                     }; |  | ||||||
|                     readData = readUntil(data, dataoffset, ';'); |  | ||||||
|                     chrs = readData[0]; |  | ||||||
|                     readdata = readData[1]; |  | ||||||
|                     dataoffset += chrs + 1; |  | ||||||
|                     break; |  | ||||||
|                 case 'n': |  | ||||||
|                     readdata = null; |  | ||||||
|                     break; |  | ||||||
|                 case 's': |  | ||||||
|                     ccount = readUntil(data, dataoffset, ':'); |  | ||||||
|                     chrs = ccount[0]; |  | ||||||
|                     stringlength = ccount[1]; |  | ||||||
|                     dataoffset += chrs + 2; |  | ||||||
| 
 |  | ||||||
|                     readData = readChrs(data, dataoffset + 1, parseInt(stringlength, 10)); |  | ||||||
|                     chrs = readData[0]; |  | ||||||
|                     readdata = readData[1]; |  | ||||||
|                     dataoffset += chrs + 2; |  | ||||||
|                     if (chrs !== parseInt(stringlength, 10) && chrs !== readdata.length) { |  | ||||||
|                         error('SyntaxError', 'String length mismatch'); |  | ||||||
|                     } |  | ||||||
|                     break; |  | ||||||
|                 case 'a': |  | ||||||
|                     readdata = {}; |  | ||||||
| 
 |  | ||||||
|                     keyandchrs = readUntil(data, dataoffset, ':'); |  | ||||||
|                     chrs = keyandchrs[0]; |  | ||||||
|                     keys = keyandchrs[1]; |  | ||||||
|                     dataoffset += chrs + 2; |  | ||||||
| 
 |  | ||||||
|                     length = parseInt(keys, 10); |  | ||||||
|                     contig = true; |  | ||||||
| 
 |  | ||||||
|                     for (let i = 0; i < length; i++) { |  | ||||||
|                         kprops = _unserialize(data, dataoffset); |  | ||||||
|                         kchrs = kprops[1]; |  | ||||||
|                         key = kprops[2]; |  | ||||||
|                         dataoffset += kchrs; |  | ||||||
| 
 |  | ||||||
|                         vprops = _unserialize(data, dataoffset); |  | ||||||
|                         vchrs = vprops[1]; |  | ||||||
|                         value = vprops[2]; |  | ||||||
|                         dataoffset += vchrs; |  | ||||||
| 
 |  | ||||||
|                         if (key !== i) { |  | ||||||
|                             contig = false; |  | ||||||
|                         } |  | ||||||
| 
 |  | ||||||
|                         readdata[key] = value; |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     if (contig) { |  | ||||||
|                         array = new Array(length); |  | ||||||
|                         for (i = 0; i < length; i++) { |  | ||||||
|                             array[i] = readdata[i]; |  | ||||||
|                         } |  | ||||||
|                         readdata = array; |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     dataoffset += 1; |  | ||||||
|                     break; |  | ||||||
|                 default: |  | ||||||
|                     error('SyntaxError', 'Unknown / Unhandled data type(s): ' + dtype); |  | ||||||
|                     break; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             return [dtype, dataoffset - offset, typeconvert(readdata)]; |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         return _unserialize((data + ''), 0)[2]; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -1138,16 +926,13 @@ export class CoreTextUtilsProvider { | |||||||
|      * @param title Title of the new state. |      * @param title Title of the new state. | ||||||
|      * @param text Content of the text to be expanded. |      * @param text Content of the text to be expanded. | ||||||
|      * @param component Component to link the embedded files to. |      * @param component Component to link the embedded files to. | ||||||
|      * @param componentId An ID to use in conjunction with the component. |      * @param options Options. | ||||||
|      * @param files List of files to display along with the text. |  | ||||||
|      * @param filter Whether the text should be filtered. |  | ||||||
|      * @param contextLevel The context level. |  | ||||||
|      * @param instanceId The instance ID related to the context. |  | ||||||
|      * @param courseId Course ID the text belongs to. It can be used to improve performance with filters. |  | ||||||
|      */ |      */ | ||||||
|  |     // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | ||||||
|     viewText(title: string, text: string, options?: CoreTextUtilsViewTextOptions): void { |     viewText(title: string, text: string, options?: CoreTextUtilsViewTextOptions): void { | ||||||
|         // @todo
 |         // @todo
 | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -1156,7 +941,7 @@ export class CoreTextUtilsProvider { | |||||||
| export type CoreTextUtilsViewTextOptions = { | export type CoreTextUtilsViewTextOptions = { | ||||||
|     component?: string; // Component to link the embedded files to.
 |     component?: string; // Component to link the embedded files to.
 | ||||||
|     componentId?: string | number; // An ID to use in conjunction with the component.
 |     componentId?: string | number; // An ID to use in conjunction with the component.
 | ||||||
|     files?: any[]; // List of files to display along with the text.
 |     files?: CoreWSExternalFile[]; // List of files to display along with the text.
 | ||||||
|     filter?: boolean; // Whether the text should be filtered.
 |     filter?: boolean; // Whether the text should be filtered.
 | ||||||
|     contextLevel?: string; // The context level.
 |     contextLevel?: string; // The context level.
 | ||||||
|     instanceId?: number; // The instance ID related to the context.
 |     instanceId?: number; // The instance ID related to the context.
 | ||||||
|  | |||||||
| @ -13,44 +13,43 @@ | |||||||
| // limitations under the License.
 | // limitations under the License.
 | ||||||
| 
 | 
 | ||||||
| import { Injectable, NgZone } from '@angular/core'; | import { Injectable, NgZone } from '@angular/core'; | ||||||
| import { InAppBrowserObject } from '@ionic-native/in-app-browser'; | import { InAppBrowserObject, InAppBrowserOptions } from '@ionic-native/in-app-browser'; | ||||||
|  | import { FileEntry } from '@ionic-native/file'; | ||||||
| import { Subscription } from 'rxjs'; | import { Subscription } from 'rxjs'; | ||||||
| 
 | 
 | ||||||
| import { CoreApp } from '@services/app'; | import { CoreApp } from '@services/app'; | ||||||
| import { CoreEvents, CoreEventsProvider } from '@services/events'; | import { CoreEvents, CoreEventsProvider } from '@services/events'; | ||||||
| import { CoreFile } from '@services/file'; | import { CoreFile } from '@services/file'; | ||||||
| import { CoreLang } from '@services/lang'; | import { CoreLang } from '@services/lang'; | ||||||
| import { CoreWS, CoreWSError } from '@services/ws'; | import { CoreWS, CoreWSError, CoreWSExternalFile } from '@services/ws'; | ||||||
| import { CoreDomUtils } from '@services/utils/dom'; | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
| import { CoreMimetypeUtils } from '@services/utils/mimetype'; | import { CoreMimetypeUtils } from '@services/utils/mimetype'; | ||||||
| import { CoreTextUtils } from '@services/utils/text'; | import { CoreTextUtils } from '@services/utils/text'; | ||||||
| import { | import { | ||||||
|     makeSingleton, Clipboard, InAppBrowser, Platform, FileOpener, WebIntent, QRScanner, Translate |     makeSingleton, Clipboard, InAppBrowser, Platform, FileOpener, WebIntent, QRScanner, Translate, | ||||||
| } from '@singletons/core.singletons'; | } from '@singletons/core.singletons'; | ||||||
| import { CoreLogger } from '@singletons/logger'; | import { CoreLogger } from '@singletons/logger'; | ||||||
| 
 | 
 | ||||||
|  | type TreeNode<T> = T & { children: TreeNode<T>[] }; | ||||||
|  | 
 | ||||||
| /* | /* | ||||||
|  * "Utils" service with helper functions. |  * "Utils" service with helper functions. | ||||||
|  */ |  */ | ||||||
| @Injectable() | @Injectable() | ||||||
| export class CoreUtilsProvider { | export class CoreUtilsProvider { | ||||||
|     protected DONT_CLONE = ['[object FileEntry]', '[object DirectoryEntry]', '[object DOMFileSystem]']; | 
 | ||||||
|  |     protected readonly DONT_CLONE = ['[object FileEntry]', '[object DirectoryEntry]', '[object DOMFileSystem]']; | ||||||
|  | 
 | ||||||
|     protected logger: CoreLogger; |     protected logger: CoreLogger; | ||||||
|     protected iabInstance: InAppBrowserObject; |     protected iabInstance: InAppBrowserObject; | ||||||
|     protected uniqueIds: {[name: string]: number} = {}; |     protected uniqueIds: {[name: string]: number} = {}; | ||||||
|     protected qrScanData: {deferred: PromiseDefer<any>, observable: Subscription}; |     protected qrScanData: {deferred: PromiseDefer<string>; observable: Subscription}; | ||||||
| 
 | 
 | ||||||
|     constructor(protected zone: NgZone) { |     constructor(protected zone: NgZone) { | ||||||
|         this.logger = CoreLogger.getInstance('CoreUtilsProvider'); |         this.logger = CoreLogger.getInstance('CoreUtilsProvider'); | ||||||
| 
 | 
 | ||||||
|         Platform.instance.ready().then(() => { |         // eslint-disable-next-line promise/catch-or-return
 | ||||||
|             const win = <any> window; |         Platform.instance.ready().then(() => this.overrideWindowOpen()); | ||||||
| 
 |  | ||||||
|             if (win.cordova && win.cordova.InAppBrowser) { |  | ||||||
|                 // Override the default window.open with the InAppBrowser one.
 |  | ||||||
|                 win.open = win.cordova.InAppBrowser.open; |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -60,7 +59,7 @@ export class CoreUtilsProvider { | |||||||
|      * @param defaultError Message to show if the error is not a string. |      * @param defaultError Message to show if the error is not a string. | ||||||
|      * @return New error message. |      * @return New error message. | ||||||
|      */ |      */ | ||||||
|     addDataNotDownloadedError(error: any, defaultError?: string): string { |     addDataNotDownloadedError(error: Error | string, defaultError?: string): string { | ||||||
|         let errorMessage = error; |         let errorMessage = error; | ||||||
| 
 | 
 | ||||||
|         if (error && typeof error != 'string') { |         if (error && typeof error != 'string') { | ||||||
| @ -85,35 +84,25 @@ export class CoreUtilsProvider { | |||||||
|      * @param promises Promises. |      * @param promises Promises. | ||||||
|      * @return Promise resolved if all promises are resolved and rejected if at least 1 promise fails. |      * @return Promise resolved if all promises are resolved and rejected if at least 1 promise fails. | ||||||
|      */ |      */ | ||||||
|     allPromises(promises: Promise<unknown>[]): Promise<void> { |     async allPromises(promises: Promise<unknown>[]): Promise<void> { | ||||||
|         if (!promises || !promises.length) { |         if (!promises || !promises.length) { | ||||||
|             return Promise.resolve(); |             return Promise.resolve(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return new Promise((resolve, reject): void => { |         const getPromiseError = async (promise): Promise<Error | void> => { | ||||||
|             const total = promises.length; |             try { | ||||||
|             let count = 0; |                 await promise; | ||||||
|             let hasFailed = false; |             } catch (error) { | ||||||
|             let error; |                 return error; | ||||||
| 
 |  | ||||||
|             promises.forEach((promise) => { |  | ||||||
|                 promise.catch((err) => { |  | ||||||
|                     hasFailed = true; |  | ||||||
|                     error = err; |  | ||||||
|                 }).finally(() => { |  | ||||||
|                     count++; |  | ||||||
| 
 |  | ||||||
|                     if (count === total) { |  | ||||||
|                         // All promises have finished, reject/resolve.
 |  | ||||||
|                         if (hasFailed) { |  | ||||||
|                             reject(error); |  | ||||||
|                         } else { |  | ||||||
|                             resolve(); |  | ||||||
|             } |             } | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         const errors = await Promise.all(promises.map(getPromiseError)); | ||||||
|  |         const error = errors.find(error => !!error); | ||||||
|  | 
 | ||||||
|  |         if (error) { | ||||||
|  |             throw error; | ||||||
|         } |         } | ||||||
|                 }); |  | ||||||
|             }); |  | ||||||
|         }); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -126,7 +115,7 @@ export class CoreUtilsProvider { | |||||||
|      * @param result Object where to put the properties. If not defined, a new object will be created. |      * @param result Object where to put the properties. If not defined, a new object will be created. | ||||||
|      * @return The object. |      * @return The object. | ||||||
|      */ |      */ | ||||||
|     arrayToObject(array: any[], propertyName?: string, result?: any): any { |     arrayToObject(array: unknown[], propertyName?: string, result?: unknown): unknown { | ||||||
|         result = result || {}; |         result = result || {}; | ||||||
|         array.forEach((entry) => { |         array.forEach((entry) => { | ||||||
|             const key = propertyName ? entry[propertyName] : entry; |             const key = propertyName ? entry[propertyName] : entry; | ||||||
| @ -148,7 +137,13 @@ export class CoreUtilsProvider { | |||||||
|      * @param undefinedIsNull True if undefined is equal to null. Defaults to true. |      * @param undefinedIsNull True if undefined is equal to null. Defaults to true. | ||||||
|      * @return Whether both items are equal. |      * @return Whether both items are equal. | ||||||
|      */ |      */ | ||||||
|     basicLeftCompare(itemA: any, itemB: any, maxLevels: number = 0, level: number = 0, undefinedIsNull: boolean = true): boolean { |     basicLeftCompare( | ||||||
|  |         itemA: any, // eslint-disable-line @typescript-eslint/no-explicit-any
 | ||||||
|  |         itemB: any, // eslint-disable-line @typescript-eslint/no-explicit-any
 | ||||||
|  |         maxLevels: number = 0, | ||||||
|  |         level: number = 0, | ||||||
|  |         undefinedIsNull: boolean = true, | ||||||
|  |     ): boolean { | ||||||
|         if (typeof itemA == 'function' || typeof itemB == 'function') { |         if (typeof itemA == 'function' || typeof itemB == 'function') { | ||||||
|             return true; // Don't compare functions.
 |             return true; // Don't compare functions.
 | ||||||
|         } else if (typeof itemA == 'object' && typeof itemB == 'object') { |         } else if (typeof itemA == 'object' && typeof itemB == 'object') { | ||||||
| @ -190,6 +185,7 @@ export class CoreUtilsProvider { | |||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Blocks leaving a view. |      * Blocks leaving a view. | ||||||
|  |      * | ||||||
|      * @deprecated, use ionViewCanLeave instead. |      * @deprecated, use ionViewCanLeave instead. | ||||||
|      */ |      */ | ||||||
|     blockLeaveView(): void { |     blockLeaveView(): void { | ||||||
| @ -202,24 +198,26 @@ export class CoreUtilsProvider { | |||||||
|      * @param url The URL to check. |      * @param url The URL to check. | ||||||
|      * @return Promise resolved with boolean_ whether there is a redirect. |      * @return Promise resolved with boolean_ whether there is a redirect. | ||||||
|      */ |      */ | ||||||
|     checkRedirect(url: string): Promise<boolean> { |     async checkRedirect(url: string): Promise<boolean> { | ||||||
|         if (window.fetch) { |         if (!window.fetch) { | ||||||
|             const win = <any> window; // Convert to <any> to be able to use AbortController (not supported by our TS version).
 |             // Cannot check if there is a redirect, assume it's false.
 | ||||||
|             const initOptions: any = { |             return false; | ||||||
|                 redirect: 'follow', |         } | ||||||
|             }; | 
 | ||||||
|             let controller; |         const initOptions: RequestInit = { redirect: 'follow' }; | ||||||
| 
 | 
 | ||||||
|         // Some browsers implement fetch but no AbortController.
 |         // Some browsers implement fetch but no AbortController.
 | ||||||
|             if (win.AbortController) { |         const controller = AbortController ? new AbortController() : false; | ||||||
|                 controller = new win.AbortController(); | 
 | ||||||
|  |         if (controller) { | ||||||
|             initOptions.signal = controller.signal; |             initOptions.signal = controller.signal; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|             return this.timeoutPromise(window.fetch(url, initOptions), CoreWS.instance.getRequestTimeout()) |         try { | ||||||
|                     .then((response: Response) => { |             const response = await this.timeoutPromise(window.fetch(url, initOptions), CoreWS.instance.getRequestTimeout()); | ||||||
|  | 
 | ||||||
|             return response.redirected; |             return response.redirected; | ||||||
|             }).catch((error) => { |         } catch (error) { | ||||||
|             if (error.timeout && controller) { |             if (error.timeout && controller) { | ||||||
|                 // Timeout, abort the request.
 |                 // Timeout, abort the request.
 | ||||||
|                 controller.abort(); |                 controller.abort(); | ||||||
| @ -227,10 +225,6 @@ export class CoreUtilsProvider { | |||||||
| 
 | 
 | ||||||
|             // There was a timeout, cannot determine if there's a redirect. Assume it's false.
 |             // There was a timeout, cannot determine if there's a redirect. Assume it's false.
 | ||||||
|             return false; |             return false; | ||||||
|             }); |  | ||||||
|         } else { |  | ||||||
|             // Cannot check if there is a redirect, assume it's false.
 |  | ||||||
|             return Promise.resolve(false); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -255,7 +249,7 @@ export class CoreUtilsProvider { | |||||||
|      * @param level Depth we are right now inside a cloned object. It's used to prevent reaching max call stack size. |      * @param level Depth we are right now inside a cloned object. It's used to prevent reaching max call stack size. | ||||||
|      * @return Cloned variable. |      * @return Cloned variable. | ||||||
|      */ |      */ | ||||||
|     clone(source: any, level: number = 0): any { |     clone<T>(source: T, level: number = 0): T { | ||||||
|         if (level >= 20) { |         if (level >= 20) { | ||||||
|             // Max 20 levels.
 |             // Max 20 levels.
 | ||||||
|             this.logger.error('Max depth reached when cloning object.', source); |             this.logger.error('Max depth reached when cloning object.', source); | ||||||
| @ -265,7 +259,7 @@ export class CoreUtilsProvider { | |||||||
| 
 | 
 | ||||||
|         if (Array.isArray(source)) { |         if (Array.isArray(source)) { | ||||||
|             // Clone the array and all the entries.
 |             // Clone the array and all the entries.
 | ||||||
|             const newArray = []; |             const newArray = [] as unknown as T; | ||||||
|             for (let i = 0; i < source.length; i++) { |             for (let i = 0; i < source.length; i++) { | ||||||
|                 newArray[i] = this.clone(source[i], level + 1); |                 newArray[i] = this.clone(source[i], level + 1); | ||||||
|             } |             } | ||||||
| @ -279,7 +273,7 @@ export class CoreUtilsProvider { | |||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             // Clone the object and all the subproperties.
 |             // Clone the object and all the subproperties.
 | ||||||
|             const newObject = {}; |             const newObject = {} as T; | ||||||
|             for (const name in source) { |             for (const name in source) { | ||||||
|                 newObject[name] = this.clone(source[name], level + 1); |                 newObject[name] = this.clone(source[name], level + 1); | ||||||
|             } |             } | ||||||
| @ -298,7 +292,7 @@ export class CoreUtilsProvider { | |||||||
|      * @param to Object where to store the properties. |      * @param to Object where to store the properties. | ||||||
|      * @param clone Whether the properties should be cloned (so they are different instances). |      * @param clone Whether the properties should be cloned (so they are different instances). | ||||||
|      */ |      */ | ||||||
|     copyProperties(from: any, to: any, clone: boolean = true): void { |     copyProperties(from: Record<string, unknown>, to: Record<string, unknown>, clone: boolean = true): void { | ||||||
|         for (const name in from) { |         for (const name in from) { | ||||||
|             if (clone) { |             if (clone) { | ||||||
|                 to[name] = this.clone(from[name]); |                 to[name] = this.clone(from[name]); | ||||||
| @ -314,13 +308,15 @@ export class CoreUtilsProvider { | |||||||
|      * @param text Text to be copied |      * @param text Text to be copied | ||||||
|      * @return Promise resolved when text is copied. |      * @return Promise resolved when text is copied. | ||||||
|      */ |      */ | ||||||
|     copyToClipboard(text: string): Promise<any> { |     async copyToClipboard(text: string): Promise<void> { | ||||||
|         return Clipboard.instance.copy(text).then(() => { |         try { | ||||||
|  |             await Clipboard.instance.copy(text); | ||||||
|  | 
 | ||||||
|             // Show toast using ionicLoading.
 |             // Show toast using ionicLoading.
 | ||||||
|             return CoreDomUtils.instance.showToast('core.copiedtoclipboard', true); |             CoreDomUtils.instance.showToast('core.copiedtoclipboard', true); | ||||||
|         }).catch(() => { |         } catch { | ||||||
|             // Ignore errors.
 |             // Ignore errors.
 | ||||||
|         }); |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -339,7 +335,7 @@ export class CoreUtilsProvider { | |||||||
|      * |      * | ||||||
|      * @param array Array to empty. |      * @param array Array to empty. | ||||||
|      */ |      */ | ||||||
|     emptyArray(array: any[]): void { |     emptyArray(array: unknown[]): void { | ||||||
|         array.length = 0; // Empty array without losing its reference.
 |         array.length = 0; // Empty array without losing its reference.
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -348,9 +344,9 @@ export class CoreUtilsProvider { | |||||||
|      * |      * | ||||||
|      * @param object Object to remove the properties. |      * @param object Object to remove the properties. | ||||||
|      */ |      */ | ||||||
|     emptyObject(object: object): void { |     emptyObject(object: Record<string, unknown>): void { | ||||||
|         for (const key in object) { |         for (const key in object) { | ||||||
|             if (object.hasOwnProperty(key)) { |             if (Object.prototype.hasOwnProperty.call(object, key)) { | ||||||
|                 delete object[key]; |                 delete object[key]; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @ -373,10 +369,8 @@ export class CoreUtilsProvider { | |||||||
|         // Execute all the processes in order.
 |         // Execute all the processes in order.
 | ||||||
|         for (const i in orderedPromisesData) { |         for (const i in orderedPromisesData) { | ||||||
|             const data = orderedPromisesData[i]; |             const data = orderedPromisesData[i]; | ||||||
|             let promise; |  | ||||||
| 
 |  | ||||||
|             // Add the process to the dependency stack.
 |             // Add the process to the dependency stack.
 | ||||||
|             promise = dependency.finally(() => { |             const promise = dependency.finally(() => { | ||||||
|                 try { |                 try { | ||||||
|                     return data.function(); |                     return data.function(); | ||||||
|                 } catch (e) { |                 } catch (e) { | ||||||
| @ -406,19 +400,19 @@ export class CoreUtilsProvider { | |||||||
|      * @param useDotNotation Whether to use dot notation '.' or square brackets '['. |      * @param useDotNotation Whether to use dot notation '.' or square brackets '['. | ||||||
|      * @return Flattened object. |      * @return Flattened object. | ||||||
|      */ |      */ | ||||||
|     flattenObject(obj: object, useDotNotation?: boolean): object { |     flattenObject(obj: Record<string, unknown>, useDotNotation?: boolean): Record<string, unknown> { | ||||||
|         const toReturn = {}; |         const toReturn = {}; | ||||||
| 
 | 
 | ||||||
|         for (const name in obj) { |         for (const name in obj) { | ||||||
|             if (!obj.hasOwnProperty(name)) { |             if (!Object.prototype.hasOwnProperty.call(obj, name)) { | ||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             const value = obj[name]; |             const value = obj[name]; | ||||||
|             if (typeof value == 'object' && !Array.isArray(value)) { |             if (typeof value == 'object' && !Array.isArray(value)) { | ||||||
|                 const flatObject = this.flattenObject(value); |                 const flatObject = this.flattenObject(value as Record<string, unknown>); | ||||||
|                 for (const subName in flatObject) { |                 for (const subName in flatObject) { | ||||||
|                     if (!flatObject.hasOwnProperty(subName)) { |                     if (!Object.prototype.hasOwnProperty.call(flatObject, subName)) { | ||||||
|                         continue; |                         continue; | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
| @ -463,19 +457,24 @@ export class CoreUtilsProvider { | |||||||
|      * @param ...args All the params sent after checkAll will be passed to isEnabledFn. |      * @param ...args All the params sent after checkAll will be passed to isEnabledFn. | ||||||
|      * @return Promise resolved with the list of enabled sites. |      * @return Promise resolved with the list of enabled sites. | ||||||
|      */ |      */ | ||||||
|     filterEnabledSites(siteIds: string[], isEnabledFn: (siteId, ...args: any[]) => boolean | Promise<boolean>, checkAll?: boolean, |     filterEnabledSites<P extends unknown[]>( | ||||||
|             ...args: any[]): Promise<string[]> { |         siteIds: string[], | ||||||
|  |         isEnabledFn: (siteId, ...args: P) => boolean | Promise<boolean>, | ||||||
|  |         checkAll?: boolean, | ||||||
|  |         ...args: P | ||||||
|  |     ): Promise<string[]> { | ||||||
|         const promises = []; |         const promises = []; | ||||||
|         const enabledSites = []; |         const enabledSites = []; | ||||||
| 
 | 
 | ||||||
|         for (const i in siteIds) { |         for (const i in siteIds) { | ||||||
|             const siteId = siteIds[i]; |             const siteId = siteIds[i]; | ||||||
|  |             const pushIfEnabled = enabled => enabled && enabledSites.push(siteId); | ||||||
|             if (checkAll || !promises.length) { |             if (checkAll || !promises.length) { | ||||||
|                 promises.push(Promise.resolve(isEnabledFn.apply(isEnabledFn, [siteId].concat(args))).then((enabled) => { |                 promises.push( | ||||||
|                     if (enabled) { |                     Promise | ||||||
|                         enabledSites.push(siteId); |                         .resolve(isEnabledFn(siteId, ...args)) | ||||||
|                     } |                         .then(pushIfEnabled), | ||||||
|                 })); |                 ); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -498,7 +497,7 @@ export class CoreUtilsProvider { | |||||||
|      * @param float The float to print. |      * @param float The float to print. | ||||||
|      * @return Locale float. |      * @return Locale float. | ||||||
|      */ |      */ | ||||||
|     formatFloat(float: any): string { |     formatFloat(float: unknown): string { | ||||||
|         if (typeof float == 'undefined' || float === null || typeof float == 'boolean') { |         if (typeof float == 'undefined' || float === null || typeof float == 'boolean') { | ||||||
|             return ''; |             return ''; | ||||||
|         } |         } | ||||||
| @ -506,9 +505,9 @@ export class CoreUtilsProvider { | |||||||
|         const localeSeparator = Translate.instance.instant('core.decsep'); |         const localeSeparator = Translate.instance.instant('core.decsep'); | ||||||
| 
 | 
 | ||||||
|         // Convert float to string.
 |         // Convert float to string.
 | ||||||
|         float += ''; |         const floatString = float + ''; | ||||||
| 
 | 
 | ||||||
|         return float.replace('.', localeSeparator); |         return floatString.replace('.', localeSeparator); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -523,17 +522,20 @@ export class CoreUtilsProvider { | |||||||
|      * @param maxDepth Max Depth to convert to tree. Children found will be in the last level of depth. |      * @param maxDepth Max Depth to convert to tree. Children found will be in the last level of depth. | ||||||
|      * @return Array with the formatted tree, children will be on each node under children field. |      * @return Array with the formatted tree, children will be on each node under children field. | ||||||
|      */ |      */ | ||||||
|     formatTree(list: any[], parentFieldName: string = 'parent', idFieldName: string = 'id', rootParentId: number = 0, |     formatTree<T>( | ||||||
|             maxDepth: number = 5): any[] { |         list: T[], | ||||||
|  |         parentFieldName: string = 'parent', | ||||||
|  |         idFieldName: string = 'id', | ||||||
|  |         rootParentId: number = 0, | ||||||
|  |         maxDepth: number = 5, | ||||||
|  |     ): TreeNode<T>[] { | ||||||
|         const map = {}; |         const map = {}; | ||||||
|         const mapDepth = {}; |         const mapDepth = {}; | ||||||
|         const tree = []; |         const tree: TreeNode<T>[] = []; | ||||||
|         let parent; |  | ||||||
|         let id; |  | ||||||
| 
 | 
 | ||||||
|         list.forEach((node, index): void => { |         list.forEach((node: TreeNode<T>, index): void => { | ||||||
|             id = node[idFieldName]; |             const id = node[idFieldName]; | ||||||
|             parent = node[parentFieldName]; |             const parent = node[parentFieldName]; | ||||||
|             node.children = []; |             node.children = []; | ||||||
| 
 | 
 | ||||||
|             if (!id || !parent) { |             if (!id || !parent) { | ||||||
| @ -543,7 +545,7 @@ export class CoreUtilsProvider { | |||||||
|             // Use map to look-up the parents.
 |             // Use map to look-up the parents.
 | ||||||
|             map[id] = index; |             map[id] = index; | ||||||
|             if (parent != rootParentId) { |             if (parent != rootParentId) { | ||||||
|                 const parentNode = list[map[parent]]; |                 const parentNode = list[map[parent]] as TreeNode<T>; | ||||||
|                 if (parentNode) { |                 if (parentNode) { | ||||||
|                     if (mapDepth[parent] == maxDepth) { |                     if (mapDepth[parent] == maxDepth) { | ||||||
|                         // Reached max level of depth. Proceed with flat order. Find parent object of the current node.
 |                         // Reached max level of depth. Proceed with flat order. Find parent object of the current node.
 | ||||||
| @ -551,11 +553,11 @@ export class CoreUtilsProvider { | |||||||
|                         if (parentOfParent) { |                         if (parentOfParent) { | ||||||
|                             // This element will be the child of the node that is two levels up the hierarchy
 |                             // This element will be the child of the node that is two levels up the hierarchy
 | ||||||
|                             // (i.e. the child of node.parent.parent).
 |                             // (i.e. the child of node.parent.parent).
 | ||||||
|                             list[map[parentOfParent]].children.push(node); |                             (list[map[parentOfParent]] as TreeNode<T>).children.push(node); | ||||||
|                             // Assign depth level to the same depth as the parent (i.e. max depth level).
 |                             // Assign depth level to the same depth as the parent (i.e. max depth level).
 | ||||||
|                             mapDepth[id] = mapDepth[parent]; |                             mapDepth[id] = mapDepth[parent]; | ||||||
|                             // Change the parent to be the one that is two levels up the hierarchy.
 |                             // Change the parent to be the one that is two levels up the hierarchy.
 | ||||||
|                             node.parent = parentOfParent; |                             node[parentFieldName] = parentOfParent; | ||||||
|                         } else { |                         } else { | ||||||
|                             this.logger.error(`Node parent of parent:${parentOfParent} not found on formatTree`); |                             this.logger.error(`Node parent of parent:${parentOfParent} not found on formatTree`); | ||||||
|                         } |                         } | ||||||
| @ -596,7 +598,7 @@ export class CoreUtilsProvider { | |||||||
|      * |      * | ||||||
|      * @return Promise resolved with the list of countries. |      * @return Promise resolved with the list of countries. | ||||||
|      */ |      */ | ||||||
|     getCountryList(): Promise<any> { |     getCountryList(): Promise<Record<string, string>> { | ||||||
|         // Get the keys of the countries.
 |         // Get the keys of the countries.
 | ||||||
|         return this.getCountryKeysList().then((keys) => { |         return this.getCountryKeysList().then((keys) => { | ||||||
|             // Now get the code and the translated name.
 |             // Now get the code and the translated name.
 | ||||||
| @ -618,7 +620,7 @@ export class CoreUtilsProvider { | |||||||
|      * |      * | ||||||
|      * @return Promise resolved with the list of countries. |      * @return Promise resolved with the list of countries. | ||||||
|      */ |      */ | ||||||
|     getCountryListSorted(): Promise<any[]> { |     getCountryListSorted(): Promise<{ code: string; name: string }[]> { | ||||||
|         // Get the keys of the countries.
 |         // Get the keys of the countries.
 | ||||||
|         return this.getCountryList().then((countries) => { |         return this.getCountryList().then((countries) => { | ||||||
|             // Sort translations.
 |             // Sort translations.
 | ||||||
| @ -647,7 +649,7 @@ export class CoreUtilsProvider { | |||||||
| 
 | 
 | ||||||
|             if (fallbackLang === defaultLang) { |             if (fallbackLang === defaultLang) { | ||||||
|                 // Same language, just reject.
 |                 // Same language, just reject.
 | ||||||
|                 return Promise.reject('Countries not found.'); |                 throw new Error('Countries not found.'); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             return this.getCountryKeysListForLanguage(fallbackLang); |             return this.getCountryKeysListForLanguage(fallbackLang); | ||||||
| @ -660,9 +662,10 @@ export class CoreUtilsProvider { | |||||||
|      * @param lang Language to check. |      * @param lang Language to check. | ||||||
|      * @return Promise resolved with the countries list. Rejected if not translated. |      * @return Promise resolved with the countries list. Rejected if not translated. | ||||||
|      */ |      */ | ||||||
|     protected getCountryKeysListForLanguage(lang: string): Promise<string[]> { |     protected async getCountryKeysListForLanguage(lang: string): Promise<string[]> { | ||||||
|         // Get the translation table for the language.
 |         // Get the translation table for the language.
 | ||||||
|         return CoreLang.instance.getTranslationTable(lang).then((table): any => { |         const table = await CoreLang.instance.getTranslationTable(lang); | ||||||
|  | 
 | ||||||
|         // Gather all the keys for countries,
 |         // Gather all the keys for countries,
 | ||||||
|         const keys = []; |         const keys = []; | ||||||
| 
 | 
 | ||||||
| @ -674,11 +677,10 @@ export class CoreUtilsProvider { | |||||||
| 
 | 
 | ||||||
|         if (keys.length === 0) { |         if (keys.length === 0) { | ||||||
|             // Not translated, reject.
 |             // Not translated, reject.
 | ||||||
|                 return Promise.reject('Countries not found.'); |             throw new Error('Countries not found.'); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return keys; |         return keys; | ||||||
|         }); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -699,9 +701,7 @@ export class CoreUtilsProvider { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Can't be guessed, get the remote mimetype.
 |         // Can't be guessed, get the remote mimetype.
 | ||||||
|         return CoreWS.instance.getRemoteFileMimeType(url).then((mimetype) => { |         return CoreWS.instance.getRemoteFileMimeType(url).then(mimetype => mimetype || ''); | ||||||
|             return mimetype || ''; |  | ||||||
|         }); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -718,13 +718,23 @@ export class CoreUtilsProvider { | |||||||
|         return ++this.uniqueIds[name]; |         return ++this.uniqueIds[name]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Check if a file is a FileEntry | ||||||
|  |      * | ||||||
|  |      * @param file File. | ||||||
|  |      * @return Type guard indicating if the file is a FileEntry. | ||||||
|  |      */ | ||||||
|  |     isFileEntry(file: FileEntry | CoreWSExternalFile): file is FileEntry { | ||||||
|  |         return 'isFile' in file; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Given a list of files, check if there are repeated names. |      * Given a list of files, check if there are repeated names. | ||||||
|      * |      * | ||||||
|      * @param files List of files. |      * @param files List of files. | ||||||
|      * @return String with error message if repeated, false if no repeated. |      * @return String with error message if repeated, false if no repeated. | ||||||
|      */ |      */ | ||||||
|     hasRepeatedFilenames(files: any[]): string | boolean { |     hasRepeatedFilenames(files: (FileEntry | CoreWSExternalFile)[]): string | false { | ||||||
|         if (!files || !files.length) { |         if (!files || !files.length) { | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
| @ -733,12 +743,14 @@ export class CoreUtilsProvider { | |||||||
| 
 | 
 | ||||||
|         // Check if there are 2 files with the same name.
 |         // Check if there are 2 files with the same name.
 | ||||||
|         for (let i = 0; i < files.length; i++) { |         for (let i = 0; i < files.length; i++) { | ||||||
|             const name = files[i].filename || files[i].name; |             const file = files[i]; | ||||||
|  |             const name = this.isFileEntry(file) ? file.name : file.filename; | ||||||
|  | 
 | ||||||
|             if (names.indexOf(name) > -1) { |             if (names.indexOf(name) > -1) { | ||||||
|                 return Translate.instance.instant('core.filenameexist', { $a: name }); |                 return Translate.instance.instant('core.filenameexist', { $a: name }); | ||||||
|             } else { |  | ||||||
|                 names.push(name); |  | ||||||
|             } |             } | ||||||
|  | 
 | ||||||
|  |             names.push(name); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return false; |         return false; | ||||||
| @ -774,6 +786,7 @@ export class CoreUtilsProvider { | |||||||
|      * @param value Value to check. |      * @param value Value to check. | ||||||
|      * @return Whether the value is false, 0 or "0". |      * @return Whether the value is false, 0 or "0". | ||||||
|      */ |      */ | ||||||
|  |     // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | ||||||
|     isFalseOrZero(value: any): boolean { |     isFalseOrZero(value: any): boolean { | ||||||
|         return typeof value != 'undefined' && (value === false || value === 'false' || parseInt(value, 10) === 0); |         return typeof value != 'undefined' && (value === false || value === 'false' || parseInt(value, 10) === 0); | ||||||
|     } |     } | ||||||
| @ -784,6 +797,7 @@ export class CoreUtilsProvider { | |||||||
|      * @param value Value to check. |      * @param value Value to check. | ||||||
|      * @return Whether the value is true, 1 or "1". |      * @return Whether the value is true, 1 or "1". | ||||||
|      */ |      */ | ||||||
|  |     // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | ||||||
|     isTrueOrOne(value: any): boolean { |     isTrueOrOne(value: any): boolean { | ||||||
|         return typeof value != 'undefined' && (value === true || value === 'true' || parseInt(value, 10) === 1); |         return typeof value != 'undefined' && (value === true || value === 'true' || parseInt(value, 10) === 1); | ||||||
|     } |     } | ||||||
| @ -794,6 +808,7 @@ export class CoreUtilsProvider { | |||||||
|      * @param error Error to check. |      * @param error Error to check. | ||||||
|      * @return Whether the error was returned by the WebService. |      * @return Whether the error was returned by the WebService. | ||||||
|      */ |      */ | ||||||
|  |     // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | ||||||
|     isWebServiceError(error: any): boolean { |     isWebServiceError(error: any): boolean { | ||||||
|         return error && (typeof error.warningcode != 'undefined' || (typeof error.errorcode != 'undefined' && |         return error && (typeof error.warningcode != 'undefined' || (typeof error.errorcode != 'undefined' && | ||||||
|                 error.errorcode != 'invalidtoken' && error.errorcode != 'userdeleted' && error.errorcode != 'upgraderunning' && |                 error.errorcode != 'invalidtoken' && error.errorcode != 'userdeleted' && error.errorcode != 'upgraderunning' && | ||||||
| @ -812,19 +827,22 @@ export class CoreUtilsProvider { | |||||||
|      * @param defaultValue Element that will become default option value. Default 0. |      * @param defaultValue Element that will become default option value. Default 0. | ||||||
|      * @return The now assembled array |      * @return The now assembled array | ||||||
|      */ |      */ | ||||||
|     makeMenuFromList(list: string, defaultLabel?: string, separator: string = ',', defaultValue?: any): any[] { |     makeMenuFromList<T>( | ||||||
|  |         list: string, | ||||||
|  |         defaultLabel?: string, | ||||||
|  |         separator: string = ',', | ||||||
|  |         defaultValue?: T, | ||||||
|  |     ): { label: string; value: T | number }[] { | ||||||
|         // Split and format the list.
 |         // Split and format the list.
 | ||||||
|         const split = list.split(separator).map((label, index) => { |         const split = list.split(separator).map((label, index) => ({ | ||||||
|             return { |  | ||||||
|             label: label.trim(), |             label: label.trim(), | ||||||
|                 value: index + 1 |             value: index + 1, | ||||||
|             }; |         })) as { label: string; value: T | number }[]; | ||||||
|         }); |  | ||||||
| 
 | 
 | ||||||
|         if (defaultLabel) { |         if (defaultLabel) { | ||||||
|             split.unshift({ |             split.unshift({ | ||||||
|                 label: defaultLabel, |                 label: defaultLabel, | ||||||
|                 value: defaultValue || 0 |                 value: defaultValue || 0, | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -839,8 +857,8 @@ export class CoreUtilsProvider { | |||||||
|      * @param [key] Key of the property that must be unique. If not specified, the whole entry. |      * @param [key] Key of the property that must be unique. If not specified, the whole entry. | ||||||
|      * @return Merged array. |      * @return Merged array. | ||||||
|      */ |      */ | ||||||
|     mergeArraysWithoutDuplicates(array1: any[], array2: any[], key?: string): any[] { |     mergeArraysWithoutDuplicates<T>(array1: T[], array2: T[], key?: string): T[] { | ||||||
|         return this.uniqueArray(array1.concat(array2), key); |         return this.uniqueArray(array1.concat(array2), key) as T[]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -849,6 +867,7 @@ export class CoreUtilsProvider { | |||||||
|      * @param value Value to check. |      * @param value Value to check. | ||||||
|      * @return True if not null and not undefined. |      * @return True if not null and not undefined. | ||||||
|      */ |      */ | ||||||
|  |     // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | ||||||
|     notNullOrUndefined(value: any): boolean { |     notNullOrUndefined(value: any): boolean { | ||||||
|         return typeof value != 'undefined' && value !== null; |         return typeof value != 'undefined' && value !== null; | ||||||
|     } |     } | ||||||
| @ -888,12 +907,10 @@ export class CoreUtilsProvider { | |||||||
| 
 | 
 | ||||||
|             if (!extension || extension.indexOf('/') > -1 || extension.indexOf('\\') > -1) { |             if (!extension || extension.indexOf('/') > -1 || extension.indexOf('\\') > -1) { | ||||||
|                 // Extension not found.
 |                 // Extension not found.
 | ||||||
|                 error = Translate.instance.instant('core.erroropenfilenoextension'); |                 throw new Error(Translate.instance.instant('core.erroropenfilenoextension')); | ||||||
|             } else { |  | ||||||
|                 error = Translate.instance.instant('core.erroropenfilenoapp'); |  | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             throw error; |             throw new Error(Translate.instance.instant('core.erroropenfilenoapp')); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -905,7 +922,7 @@ export class CoreUtilsProvider { | |||||||
|      * @param options Override default options passed to InAppBrowser. |      * @param options Override default options passed to InAppBrowser. | ||||||
|      * @return The opened window. |      * @return The opened window. | ||||||
|      */ |      */ | ||||||
|     openInApp(url: string, options?: any): InAppBrowserObject { |     openInApp(url: string, options?: InAppBrowserOptions): InAppBrowserObject { | ||||||
|         if (!url) { |         if (!url) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| @ -995,36 +1012,32 @@ export class CoreUtilsProvider { | |||||||
|      * @param url The URL of the file. |      * @param url The URL of the file. | ||||||
|      * @return Promise resolved when opened. |      * @return Promise resolved when opened. | ||||||
|      */ |      */ | ||||||
|     openOnlineFile(url: string): Promise<void> { |     async openOnlineFile(url: string): Promise<void> { | ||||||
|         if (CoreApp.instance.isAndroid()) { |         if (CoreApp.instance.isAndroid()) { | ||||||
|             // In Android we need the mimetype to open it.
 |             // In Android we need the mimetype to open it.
 | ||||||
|             return this.getMimeTypeFromUrl(url).catch(() => { |             const mimetype = await this.ignoreErrors(this.getMimeTypeFromUrl(url)); | ||||||
|                 // Error getting mimetype, return undefined.
 | 
 | ||||||
|             }).then((mimetype) => { |  | ||||||
|             if (!mimetype) { |             if (!mimetype) { | ||||||
|                 // Couldn't retrieve mimetype. Return error.
 |                 // Couldn't retrieve mimetype. Return error.
 | ||||||
|                     return Promise.reject(Translate.instance.instant('core.erroropenfilenoextension')); |                 throw new Error(Translate.instance.instant('core.erroropenfilenoextension')); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             const options = { |             const options = { | ||||||
|                 action: WebIntent.instance.ACTION_VIEW, |                 action: WebIntent.instance.ACTION_VIEW, | ||||||
|                 url, |                 url, | ||||||
|                     type: mimetype |                 type: mimetype, | ||||||
|             }; |             }; | ||||||
| 
 | 
 | ||||||
|             return WebIntent.instance.startActivity(options).catch((error) => { |             return WebIntent.instance.startActivity(options).catch((error) => { | ||||||
|                 this.logger.error('Error opening online file ' + url + ' with mimetype ' + mimetype); |                 this.logger.error('Error opening online file ' + url + ' with mimetype ' + mimetype); | ||||||
|                 this.logger.error('Error: ', JSON.stringify(error)); |                 this.logger.error('Error: ', JSON.stringify(error)); | ||||||
| 
 | 
 | ||||||
|                     return Promise.reject(Translate.instance.instant('core.erroropenfilenoapp')); |                 throw new Error(Translate.instance.instant('core.erroropenfilenoapp')); | ||||||
|                 }); |  | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // In the rest of platforms we need to open them in InAppBrowser.
 |         // In the rest of platforms we need to open them in InAppBrowser.
 | ||||||
|         this.openInApp(url); |         this.openInApp(url); | ||||||
| 
 |  | ||||||
|         return Promise.resolve(); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -1033,10 +1046,8 @@ export class CoreUtilsProvider { | |||||||
|      * @param obj Object to convert. |      * @param obj Object to convert. | ||||||
|      * @return Array with the values of the object but losing the keys. |      * @return Array with the values of the object but losing the keys. | ||||||
|      */ |      */ | ||||||
|     objectToArray(obj: object): any[] { |     objectToArray<T>(obj: Record<string, T>): T[] { | ||||||
|         return Object.keys(obj).map((key) => { |         return Object.keys(obj).map((key) => obj[key]); | ||||||
|             return obj[key]; |  | ||||||
|         }); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -1051,9 +1062,15 @@ export class CoreUtilsProvider { | |||||||
|      * @param sortByValue True to sort values alphabetically, false otherwise. |      * @param sortByValue True to sort values alphabetically, false otherwise. | ||||||
|      * @return Array of objects with the name & value of each property. |      * @return Array of objects with the name & value of each property. | ||||||
|      */ |      */ | ||||||
|     objectToArrayOfObjects(obj: object, keyName: string, valueName: string, sortByKey?: boolean, sortByValue?: boolean): any[] { |     objectToArrayOfObjects( | ||||||
|  |         obj: Record<string, unknown>, | ||||||
|  |         keyName: string, | ||||||
|  |         valueName: string, | ||||||
|  |         sortByKey?: boolean, | ||||||
|  |         sortByValue?: boolean, | ||||||
|  |     ): Record<string, unknown>[] { | ||||||
|         // Get the entries from an object or primitive value.
 |         // Get the entries from an object or primitive value.
 | ||||||
|         const getEntries = (elKey, value): any[] | any => { |         const getEntries = (elKey: string, value: unknown): Record<string, unknown>[] | unknown => { | ||||||
|             if (typeof value == 'undefined' || value == null) { |             if (typeof value == 'undefined' || value == null) { | ||||||
|                 // Filter undefined and null values.
 |                 // Filter undefined and null values.
 | ||||||
|                 return; |                 return; | ||||||
| @ -1087,7 +1104,7 @@ export class CoreUtilsProvider { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // "obj" will always be an object, so "entries" will always be an array.
 |         // "obj" will always be an object, so "entries" will always be an array.
 | ||||||
|         const entries = <any[]> getEntries('', obj); |         const entries = getEntries('', obj) as Record<string, unknown>[]; | ||||||
|         if (sortByKey || sortByValue) { |         if (sortByKey || sortByValue) { | ||||||
|             return entries.sort((a, b) => { |             return entries.sort((a, b) => { | ||||||
|                 if (sortByKey) { |                 if (sortByKey) { | ||||||
| @ -1111,7 +1128,12 @@ export class CoreUtilsProvider { | |||||||
|      * @param keyPrefix Key prefix if neededs to delete it. |      * @param keyPrefix Key prefix if neededs to delete it. | ||||||
|      * @return Object. |      * @return Object. | ||||||
|      */ |      */ | ||||||
|     objectToKeyValueMap(objects: object[], keyName: string, valueName: string, keyPrefix?: string): {[name: string]: any} { |     objectToKeyValueMap( | ||||||
|  |         objects: Record<string, unknown>[], | ||||||
|  |         keyName: string, | ||||||
|  |         valueName: string, | ||||||
|  |         keyPrefix?: string, | ||||||
|  |     ): {[name: string]: unknown} { | ||||||
|         if (!objects) { |         if (!objects) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| @ -1119,7 +1141,8 @@ export class CoreUtilsProvider { | |||||||
|         const prefixSubstr = keyPrefix ? keyPrefix.length : 0; |         const prefixSubstr = keyPrefix ? keyPrefix.length : 0; | ||||||
|         const mapped = {}; |         const mapped = {}; | ||||||
|         objects.forEach((item) => { |         objects.forEach((item) => { | ||||||
|             const key = prefixSubstr > 0 ? item[keyName].substr(prefixSubstr) : item[keyName]; |             const keyValue = item[keyName] as string; | ||||||
|  |             const key = prefixSubstr > 0 ? keyValue.substr(prefixSubstr) : keyValue; | ||||||
|             mapped[key] = item[valueName]; |             mapped[key] = item[valueName]; | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
| @ -1133,7 +1156,7 @@ export class CoreUtilsProvider { | |||||||
|      * @param removeEmpty Whether to remove params whose value is null/undefined. |      * @param removeEmpty Whether to remove params whose value is null/undefined. | ||||||
|      * @return GET params. |      * @return GET params. | ||||||
|      */ |      */ | ||||||
|     objectToGetParams(object: any, removeEmpty: boolean = true): string { |     objectToGetParams(object: Record<string, unknown>, removeEmpty: boolean = true): string { | ||||||
|         // First of all, flatten the object so all properties are in the first level.
 |         // First of all, flatten the object so all properties are in the first level.
 | ||||||
|         const flattened = this.flattenObject(object); |         const flattened = this.flattenObject(object); | ||||||
|         let result = ''; |         let result = ''; | ||||||
| @ -1164,7 +1187,7 @@ export class CoreUtilsProvider { | |||||||
|      * @param prefix Prefix to add. |      * @param prefix Prefix to add. | ||||||
|      * @return Prefixed object. |      * @return Prefixed object. | ||||||
|      */ |      */ | ||||||
|     prefixKeys(data: any, prefix: string): any { |     prefixKeys(data: Record<string, unknown>, prefix: string): Record<string, unknown> { | ||||||
|         const newObj = {}; |         const newObj = {}; | ||||||
|         const keys = Object.keys(data); |         const keys = Object.keys(data); | ||||||
| 
 | 
 | ||||||
| @ -1196,12 +1219,14 @@ export class CoreUtilsProvider { | |||||||
|      * @param promise Promise to check |      * @param promise Promise to check | ||||||
|      * @return Promise resolved with boolean: true if the promise is rejected or false if it's resolved. |      * @return Promise resolved with boolean: true if the promise is rejected or false if it's resolved. | ||||||
|      */ |      */ | ||||||
|     promiseFails(promise: Promise<any>): Promise<boolean> { |     async promiseFails(promise: Promise<unknown>): Promise<boolean> { | ||||||
|         return promise.then(() => { |         try { | ||||||
|  |             await promise; | ||||||
|  | 
 | ||||||
|             return false; |             return false; | ||||||
|         }).catch(() => { |         } catch { | ||||||
|             return true; |             return true; | ||||||
|         }); |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -1210,12 +1235,14 @@ export class CoreUtilsProvider { | |||||||
|      * @param promise Promise to check |      * @param promise Promise to check | ||||||
|      * @return Promise resolved with boolean: true if the promise it's resolved or false if it's rejected. |      * @return Promise resolved with boolean: true if the promise it's resolved or false if it's rejected. | ||||||
|      */ |      */ | ||||||
|     promiseWorks(promise: Promise<any>): Promise<boolean> { |     async promiseWorks(promise: Promise<unknown>): Promise<boolean> { | ||||||
|         return promise.then(() => { |         try { | ||||||
|  |             await promise; | ||||||
|  | 
 | ||||||
|             return true; |             return true; | ||||||
|         }).catch(() => { |         } catch { | ||||||
|             return false; |             return false; | ||||||
|         }); |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -1228,7 +1255,7 @@ export class CoreUtilsProvider { | |||||||
|      * @param key Key to check. |      * @param key Key to check. | ||||||
|      * @return Whether the two objects/arrays have the same value (or lack of one) for a given key. |      * @return Whether the two objects/arrays have the same value (or lack of one) for a given key. | ||||||
|      */ |      */ | ||||||
|     sameAtKeyMissingIsBlank(obj1: any, obj2: any, key: string): boolean { |     sameAtKeyMissingIsBlank(obj1: unknown, obj2: unknown, key: string): boolean { | ||||||
|         let value1 = typeof obj1[key] != 'undefined' ? obj1[key] : ''; |         let value1 = typeof obj1[key] != 'undefined' ? obj1[key] : ''; | ||||||
|         let value2 = typeof obj2[key] != 'undefined' ? obj2[key] : ''; |         let value2 = typeof obj2[key] != 'undefined' ? obj2[key] : ''; | ||||||
| 
 | 
 | ||||||
| @ -1249,7 +1276,7 @@ export class CoreUtilsProvider { | |||||||
|      * @param obj Object to stringify. |      * @param obj Object to stringify. | ||||||
|      * @return Stringified object. |      * @return Stringified object. | ||||||
|      */ |      */ | ||||||
|     sortAndStringify(obj: object): string { |     sortAndStringify(obj: Record<string, unknown>): string { | ||||||
|         return JSON.stringify(this.sortProperties(obj)); |         return JSON.stringify(this.sortProperties(obj)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -1259,7 +1286,7 @@ export class CoreUtilsProvider { | |||||||
|      * @param obj The object to sort. If it isn't an object, the original value will be returned. |      * @param obj The object to sort. If it isn't an object, the original value will be returned. | ||||||
|      * @return Sorted object. |      * @return Sorted object. | ||||||
|      */ |      */ | ||||||
|     sortProperties(obj: object): object { |     sortProperties<T>(obj: T): T { | ||||||
|         if (obj != null && typeof obj == 'object' && !Array.isArray(obj)) { |         if (obj != null && typeof obj == 'object' && !Array.isArray(obj)) { | ||||||
|             // It's an object, sort it.
 |             // It's an object, sort it.
 | ||||||
|             return Object.keys(obj).sort().reduce((accumulator, key) => { |             return Object.keys(obj).sort().reduce((accumulator, key) => { | ||||||
| @ -1267,7 +1294,7 @@ export class CoreUtilsProvider { | |||||||
|                 accumulator[key] = this.sortProperties(obj[key]); |                 accumulator[key] = this.sortProperties(obj[key]); | ||||||
| 
 | 
 | ||||||
|                 return accumulator; |                 return accumulator; | ||||||
|             }, {}); |             }, {} as T); | ||||||
|         } else { |         } else { | ||||||
|             return obj; |             return obj; | ||||||
|         } |         } | ||||||
| @ -1279,12 +1306,12 @@ export class CoreUtilsProvider { | |||||||
|      * @param obj The object to sort. If it isn't an object, the original value will be returned. |      * @param obj The object to sort. If it isn't an object, the original value will be returned. | ||||||
|      * @return Sorted object. |      * @return Sorted object. | ||||||
|      */ |      */ | ||||||
|     sortValues(obj: object): object { |     sortValues<T>(obj: T): T { | ||||||
|         if (typeof obj == 'object' && !Array.isArray(obj)) { |         if (typeof obj == 'object' && !Array.isArray(obj)) { | ||||||
|             // It's an object, sort it. Convert it to an array to be able to sort it and then convert it back to object.
 |             // It's an object, sort it. Convert it to an array to be able to sort it and then convert it back to object.
 | ||||||
|             const array = this.objectToArrayOfObjects(obj, 'name', 'value', false, true); |             const array = this.objectToArrayOfObjects(obj as Record<string, unknown>, 'name', 'value', false, true); | ||||||
| 
 | 
 | ||||||
|             return this.objectToKeyValueMap(array, 'name', 'value'); |             return this.objectToKeyValueMap(array, 'name', 'value') as unknown as T; | ||||||
|         } else { |         } else { | ||||||
|             return obj; |             return obj; | ||||||
|         } |         } | ||||||
| @ -1297,10 +1324,10 @@ export class CoreUtilsProvider { | |||||||
|      * @return File size and a boolean to indicate if it is the total size or only partial. |      * @return File size and a boolean to indicate if it is the total size or only partial. | ||||||
|      * @deprecated since 3.8.0. Use CorePluginFileDelegate.getFilesSize instead. |      * @deprecated since 3.8.0. Use CorePluginFileDelegate.getFilesSize instead. | ||||||
|      */ |      */ | ||||||
|     sumFileSizes(files: any[]): { size: number, total: boolean } { |     sumFileSizes(files: CoreWSExternalFile[]): { size: number; total: boolean } { | ||||||
|         const result = { |         const result = { | ||||||
|             size: 0, |             size: 0, | ||||||
|             total: true |             total: true, | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         files.forEach((file) => { |         files.forEach((file) => { | ||||||
| @ -1325,13 +1352,25 @@ export class CoreUtilsProvider { | |||||||
|      */ |      */ | ||||||
|     timeoutPromise<T>(promise: Promise<T>, time: number): Promise<T> { |     timeoutPromise<T>(promise: Promise<T>, time: number): Promise<T> { | ||||||
|         return new Promise((resolve, reject): void => { |         return new Promise((resolve, reject): void => { | ||||||
|             const timeout = setTimeout(() => { |             let timedOut = false; | ||||||
|  |             const resolveBeforeTimeout = () => { | ||||||
|  |                 if (timedOut) { | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |                 resolve(); | ||||||
|  |             }; | ||||||
|  |             const timeout = setTimeout( | ||||||
|  |                 () => { | ||||||
|                     reject({ timeout: true }); |                     reject({ timeout: true }); | ||||||
|             }, time); |                     timedOut = true; | ||||||
|  |                 }, | ||||||
|  |                 time, | ||||||
|  |             ); | ||||||
| 
 | 
 | ||||||
|             promise.then(resolve).catch(reject).finally(() => { |             promise | ||||||
|                 clearTimeout(timeout); |                 .then(resolveBeforeTimeout) | ||||||
|             }); |                 .catch(reject) | ||||||
|  |                 .finally(() => clearTimeout(timeout)); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -1344,7 +1383,8 @@ export class CoreUtilsProvider { | |||||||
|      * @param strict If true, then check the input and return false if it is not a valid number. |      * @param strict If true, then check the input and return false if it is not a valid number. | ||||||
|      * @return False if bad format, empty string if empty value or the parsed float if not. |      * @return False if bad format, empty string if empty value or the parsed float if not. | ||||||
|      */ |      */ | ||||||
|     unformatFloat(localeFloat: any, strict?: boolean): any { |     // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | ||||||
|  |     unformatFloat(localeFloat: any, strict?: boolean): false | '' | number { | ||||||
|         // Bad format on input type number.
 |         // Bad format on input type number.
 | ||||||
|         if (typeof localeFloat == 'undefined') { |         if (typeof localeFloat == 'undefined') { | ||||||
|             return false; |             return false; | ||||||
| @ -1383,7 +1423,7 @@ export class CoreUtilsProvider { | |||||||
|      * @param [key] Key of the property that must be unique. If not specified, the whole entry. |      * @param [key] Key of the property that must be unique. If not specified, the whole entry. | ||||||
|      * @return Array without duplicate values. |      * @return Array without duplicate values. | ||||||
|      */ |      */ | ||||||
|     uniqueArray(array: any[], key?: string): any[] { |     uniqueArray<T>(array: T[], key?: string): T[] { | ||||||
|         const filtered = []; |         const filtered = []; | ||||||
|         const unique = {}; // Use an object to make it faster to check if it's duplicate.
 |         const unique = {}; // Use an object to make it faster to check if it's duplicate.
 | ||||||
| 
 | 
 | ||||||
| @ -1407,16 +1447,13 @@ export class CoreUtilsProvider { | |||||||
|      * @param delay Time that must pass until the function is called. |      * @param delay Time that must pass until the function is called. | ||||||
|      * @return Debounced function. |      * @return Debounced function. | ||||||
|      */ |      */ | ||||||
|     debounce(fn: (...args: any[]) => any, delay: number): (...args: any[]) => void { |     debounce<T extends unknown[]>(fn: (...args: T) => unknown, delay: number): (...args: T) => void { | ||||||
| 
 |  | ||||||
|         let timeoutID: number; |         let timeoutID: number; | ||||||
| 
 | 
 | ||||||
|         const debounced = (...args: any[]): void => { |         const debounced = (...args: unknown[]): void => { | ||||||
|             clearTimeout(timeoutID); |             clearTimeout(timeoutID); | ||||||
| 
 | 
 | ||||||
|             timeoutID = window.setTimeout(() => { |             timeoutID = window.setTimeout(() => fn.apply(null, args), delay); | ||||||
|                 fn.apply(null, args); |  | ||||||
|             }, delay); |  | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         return debounced; |         return debounced; | ||||||
| @ -1448,18 +1485,19 @@ export class CoreUtilsProvider { | |||||||
|      * |      * | ||||||
|      * @return Promise resolved with the QR string, rejected if error or cancelled. |      * @return Promise resolved with the QR string, rejected if error or cancelled. | ||||||
|      */ |      */ | ||||||
|     startScanQR(): Promise<string> { |     async startScanQR(): Promise<string> { | ||||||
|         if (!CoreApp.instance.isMobile()) { |         if (!CoreApp.instance.isMobile()) { | ||||||
|             return Promise.reject('QRScanner isn\'t available in desktop apps.'); |             return Promise.reject('QRScanner isn\'t available in desktop apps.'); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Ask the user for permission to use the camera.
 |         // Ask the user for permission to use the camera.
 | ||||||
|         // The scan method also does this, but since it returns an Observable we wouldn't be able to detect if the user denied.
 |         // The scan method also does this, but since it returns an Observable we wouldn't be able to detect if the user denied.
 | ||||||
|         return QRScanner.instance.prepare().then((status) => { |         try { | ||||||
|  |             const status = await QRScanner.instance.prepare(); | ||||||
| 
 | 
 | ||||||
|             if (!status.authorized) { |             if (!status.authorized) { | ||||||
|                 // No access to the camera, reject. In android this shouldn't happen, denying access passes through catch.
 |                 // No access to the camera, reject. In android this shouldn't happen, denying access passes through catch.
 | ||||||
|                 return Promise.reject('The user denied camera access.'); |                 throw new Error('The user denied camera access.'); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             if (this.qrScanData && this.qrScanData.deferred) { |             if (this.qrScanData && this.qrScanData.deferred) { | ||||||
| @ -1470,29 +1508,29 @@ export class CoreUtilsProvider { | |||||||
|             // Start scanning.
 |             // Start scanning.
 | ||||||
|             this.qrScanData = { |             this.qrScanData = { | ||||||
|                 deferred: this.promiseDefer(), |                 deferred: this.promiseDefer(), | ||||||
|                 observable: QRScanner.instance.scan().subscribe((text) => { |  | ||||||
| 
 | 
 | ||||||
|                     // Text received, stop scanning and return the text.
 |                 // When text is received, stop scanning and return the text.
 | ||||||
|                     this.stopScanQR(text, false); |                 observable: QRScanner.instance.scan().subscribe(text => this.stopScanQR(text, false)), | ||||||
|                 }) |  | ||||||
|             }; |             }; | ||||||
| 
 | 
 | ||||||
|             // Show the camera.
 |             // Show the camera.
 | ||||||
|             return QRScanner.instance.show().then(() => { |             try { | ||||||
|  |                 await QRScanner.instance.show(); | ||||||
|  | 
 | ||||||
|                 document.body.classList.add('core-scanning-qr'); |                 document.body.classList.add('core-scanning-qr'); | ||||||
| 
 | 
 | ||||||
|                 return this.qrScanData.deferred.promise; |                 return this.qrScanData.deferred.promise; | ||||||
|             }, (err) => { |             } catch (e) { | ||||||
|                 this.stopScanQR(err, true); |                 this.stopScanQR(e, true); | ||||||
| 
 | 
 | ||||||
|                 return Promise.reject(err); |                 throw e; | ||||||
|             }); |             } | ||||||
|  |         } catch (error) { | ||||||
|  |             // eslint-disable-next-line no-underscore-dangle, @typescript-eslint/naming-convention
 | ||||||
|  |             error.message = error.message || (error as { _message?: string })._message; | ||||||
| 
 | 
 | ||||||
|         }).catch((err) => { |             throw error; | ||||||
|             err.message = err.message || err._message; |         } | ||||||
| 
 |  | ||||||
|             return Promise.reject(err); |  | ||||||
|         }); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -1501,8 +1539,7 @@ export class CoreUtilsProvider { | |||||||
|      * @param data If success, the text of the QR code. If error, the error object or message. Undefined for cancelled. |      * @param data If success, the text of the QR code. If error, the error object or message. Undefined for cancelled. | ||||||
|      * @param error True if the data belongs to an error, false otherwise. |      * @param error True if the data belongs to an error, false otherwise. | ||||||
|      */ |      */ | ||||||
|     stopScanQR(data?: any, error?: boolean): void { |     stopScanQR(data?: string | Error, error?: boolean): void { | ||||||
| 
 |  | ||||||
|         if (!this.qrScanData) { |         if (!this.qrScanData) { | ||||||
|             // Not scanning.
 |             // Not scanning.
 | ||||||
|             return; |             return; | ||||||
| @ -1518,7 +1555,7 @@ export class CoreUtilsProvider { | |||||||
|         if (error) { |         if (error) { | ||||||
|             this.qrScanData.deferred.reject(data); |             this.qrScanData.deferred.reject(data); | ||||||
|         } else if (typeof data != 'undefined') { |         } else if (typeof data != 'undefined') { | ||||||
|             this.qrScanData.deferred.resolve(data); |             this.qrScanData.deferred.resolve(data as string); | ||||||
|         } else { |         } else { | ||||||
|             this.qrScanData.deferred.reject(CoreDomUtils.instance.createCanceledError()); |             this.qrScanData.deferred.reject(CoreDomUtils.instance.createCanceledError()); | ||||||
|         } |         } | ||||||
| @ -1549,10 +1586,20 @@ export class CoreUtilsProvider { | |||||||
|      * @return Promise resolved after the time has passed. |      * @return Promise resolved after the time has passed. | ||||||
|      */ |      */ | ||||||
|     wait(milliseconds: number): Promise<void> { |     wait(milliseconds: number): Promise<void> { | ||||||
|         return new Promise((resolve, reject): void => { |         return new Promise(resolve => setTimeout(resolve, milliseconds)); | ||||||
|             setTimeout(resolve, milliseconds); |  | ||||||
|         }); |  | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Override native window.open with InAppBrowser if available. | ||||||
|  |      */ | ||||||
|  |     private overrideWindowOpen() { | ||||||
|  |         if (!window.cordova?.InAppBrowser) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         window.open = window.cordova.InAppBrowser.open; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export class CoreUtils extends makeSingleton(CoreUtilsProvider) {} | export class CoreUtils extends makeSingleton(CoreUtilsProvider) {} | ||||||
|  | |||||||
							
								
								
									
										447
									
								
								src/app/singletons/locutus.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										447
									
								
								src/app/singletons/locutus.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,447 @@ | |||||||
|  | /* eslint-disable */ | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Original code taken from https://github.com/kvz/locutus
 | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | function initCache () { | ||||||
|  |   const store = [] | ||||||
|  |   // cache only first element, second is length to jump ahead for the parser
 | ||||||
|  |   const cache = function cache (value) { | ||||||
|  |     store.push(value[0]) | ||||||
|  |     return value | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   cache.get = (index) => { | ||||||
|  |     if (index >= store.length) { | ||||||
|  |       throw RangeError(`Can't resolve reference ${index + 1}`) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return store[index] | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return cache | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function expectType (str, cache) { | ||||||
|  |   const types = /^(?:N(?=;)|[bidsSaOCrR](?=:)|[^:]+(?=:))/g | ||||||
|  |   const type = (types.exec(str) || [])[0] | ||||||
|  | 
 | ||||||
|  |   if (!type) { | ||||||
|  |     throw SyntaxError('Invalid input: ' + str) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   switch (type) { | ||||||
|  |     case 'N': | ||||||
|  |       return cache([ null, 2 ]) | ||||||
|  |     case 'b': | ||||||
|  |       return cache(expectBool(str)) | ||||||
|  |     case 'i': | ||||||
|  |       return cache(expectInt(str)) | ||||||
|  |     case 'd': | ||||||
|  |       return cache(expectFloat(str)) | ||||||
|  |     case 's': | ||||||
|  |       return cache(expectString(str)) | ||||||
|  |     case 'S': | ||||||
|  |       return cache(expectEscapedString(str)) | ||||||
|  |     case 'a': | ||||||
|  |       return expectArray(str, cache) | ||||||
|  |     case 'O': | ||||||
|  |       return expectObject(str, cache) | ||||||
|  |     case 'C': | ||||||
|  |       return expectClass(str, cache) | ||||||
|  |     case 'r': | ||||||
|  |     case 'R': | ||||||
|  |       return expectReference(str, cache) | ||||||
|  |     default: | ||||||
|  |       throw SyntaxError(`Invalid or unsupported data type: ${type}`) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function expectBool (str) { | ||||||
|  |   const reBool = /^b:([01]);/ | ||||||
|  |   const [ match, boolMatch ] = reBool.exec(str) || [] | ||||||
|  | 
 | ||||||
|  |   if (!boolMatch) { | ||||||
|  |     throw SyntaxError('Invalid bool value, expected 0 or 1') | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return [ boolMatch === '1', match.length ] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function expectInt (str) { | ||||||
|  |   const reInt = /^i:([+-]?\d+);/ | ||||||
|  |   const [ match, intMatch ] = reInt.exec(str) || [] | ||||||
|  | 
 | ||||||
|  |   if (!intMatch) { | ||||||
|  |     throw SyntaxError('Expected an integer value') | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return [ parseInt(intMatch, 10), match.length ] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function expectFloat (str) { | ||||||
|  |   const reFloat = /^d:(NAN|-?INF|(?:\d+\.\d*|\d*\.\d+|\d+)(?:[eE][+-]\d+)?);/ | ||||||
|  |   const [ match, floatMatch ] = reFloat.exec(str) || [] | ||||||
|  | 
 | ||||||
|  |   if (!floatMatch) { | ||||||
|  |     throw SyntaxError('Expected a float value') | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   let floatValue | ||||||
|  | 
 | ||||||
|  |   switch (floatMatch) { | ||||||
|  |     case 'NAN': | ||||||
|  |       floatValue = Number.NaN | ||||||
|  |       break | ||||||
|  |     case '-INF': | ||||||
|  |       floatValue = Number.NEGATIVE_INFINITY | ||||||
|  |       break | ||||||
|  |     case 'INF': | ||||||
|  |       floatValue = Number.POSITIVE_INFINITY | ||||||
|  |       break | ||||||
|  |     default: | ||||||
|  |       floatValue = parseFloat(floatMatch) | ||||||
|  |       break | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return [ floatValue, match.length ] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function readBytes (str, len, escapedString = false) { | ||||||
|  |   let bytes = 0 | ||||||
|  |   let out = '' | ||||||
|  |   let c = 0 | ||||||
|  |   const strLen = str.length | ||||||
|  |   let wasHighSurrogate = false | ||||||
|  |   let escapedChars = 0 | ||||||
|  | 
 | ||||||
|  |   while (bytes < len && c < strLen) { | ||||||
|  |     let chr = str.charAt(c) | ||||||
|  |     const code = chr.charCodeAt(0) | ||||||
|  |     const isHighSurrogate = code >= 0xd800 && code <= 0xdbff | ||||||
|  |     const isLowSurrogate = code >= 0xdc00 && code <= 0xdfff | ||||||
|  | 
 | ||||||
|  |     if (escapedString && chr === '\\') { | ||||||
|  |       chr = String.fromCharCode(parseInt(str.substr(c + 1, 2), 16)) | ||||||
|  |       escapedChars++ | ||||||
|  | 
 | ||||||
|  |       // each escaped sequence is 3 characters. Go 2 chars ahead.
 | ||||||
|  |       // third character will be jumped over a few lines later
 | ||||||
|  |       c += 2 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     c++ | ||||||
|  | 
 | ||||||
|  |     bytes += isHighSurrogate || (isLowSurrogate && wasHighSurrogate) | ||||||
|  |       // if high surrogate, count 2 bytes, as expectation is to be followed by low surrogate
 | ||||||
|  |       // if low surrogate preceded by high surrogate, add 2 bytes
 | ||||||
|  |       ? 2 | ||||||
|  |       : code > 0x7ff | ||||||
|  |         // otherwise low surrogate falls into this part
 | ||||||
|  |         ? 3 | ||||||
|  |         : code > 0x7f | ||||||
|  |           ? 2 | ||||||
|  |           : 1 | ||||||
|  | 
 | ||||||
|  |     // if high surrogate is not followed by low surrogate, add 1 more byte
 | ||||||
|  |     bytes += wasHighSurrogate && !isLowSurrogate ? 1 : 0 | ||||||
|  | 
 | ||||||
|  |     out += chr | ||||||
|  |     wasHighSurrogate = isHighSurrogate | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return [ out, bytes, escapedChars ] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function expectString (str) { | ||||||
|  |   // PHP strings consist of one-byte characters.
 | ||||||
|  |   // JS uses 2 bytes with possible surrogate pairs.
 | ||||||
|  |   // Serialized length of 2 is still 1 JS string character
 | ||||||
|  |   const reStrLength = /^s:(\d+):"/g // also match the opening " char
 | ||||||
|  |   const [ match, byteLenMatch ] = reStrLength.exec(str) || [] | ||||||
|  | 
 | ||||||
|  |   if (!match) { | ||||||
|  |     throw SyntaxError('Expected a string value') | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const len = parseInt(byteLenMatch, 10) | ||||||
|  | 
 | ||||||
|  |   str = str.substr(match.length) | ||||||
|  | 
 | ||||||
|  |   let [ strMatch, bytes ] = readBytes(str, len) | ||||||
|  | 
 | ||||||
|  |   if (bytes !== len) { | ||||||
|  |     throw SyntaxError(`Expected string of ${len} bytes, but got ${bytes}`) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   str = str.substr((strMatch as string).length) | ||||||
|  | 
 | ||||||
|  |   // strict parsing, match closing "; chars
 | ||||||
|  |   if (!str.startsWith('";')) { | ||||||
|  |     throw SyntaxError('Expected ";') | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return [ strMatch, match.length + (strMatch as string).length + 2 ] // skip last ";
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function expectEscapedString (str) { | ||||||
|  |   const reStrLength = /^S:(\d+):"/g // also match the opening " char
 | ||||||
|  |   const [ match, strLenMatch ] = reStrLength.exec(str) || [] | ||||||
|  | 
 | ||||||
|  |   if (!match) { | ||||||
|  |     throw SyntaxError('Expected an escaped string value') | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const len = parseInt(strLenMatch, 10) | ||||||
|  | 
 | ||||||
|  |   str = str.substr(match.length) | ||||||
|  | 
 | ||||||
|  |   let [ strMatch, bytes, escapedChars ] = readBytes(str, len, true) | ||||||
|  | 
 | ||||||
|  |   if (bytes !== len) { | ||||||
|  |     throw SyntaxError(`Expected escaped string of ${len} bytes, but got ${bytes}`) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   str = str.substr((strMatch as string).length + (escapedChars as number) * 2) | ||||||
|  | 
 | ||||||
|  |   // strict parsing, match closing "; chars
 | ||||||
|  |   if (!str.startsWith('";')) { | ||||||
|  |     throw SyntaxError('Expected ";') | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return [ strMatch, match.length + (strMatch as string).length + 2 ] // skip last ";
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function expectKeyOrIndex (str) { | ||||||
|  |   try { | ||||||
|  |     return expectString(str) | ||||||
|  |   } catch (err) {} | ||||||
|  | 
 | ||||||
|  |   try { | ||||||
|  |     return expectEscapedString(str) | ||||||
|  |   } catch (err) {} | ||||||
|  | 
 | ||||||
|  |   try { | ||||||
|  |     return expectInt(str) | ||||||
|  |   } catch (err) { | ||||||
|  |     throw SyntaxError('Expected key or index') | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function expectObject (str, cache) { | ||||||
|  |   // O:<class name length>:"class name":<prop count>:{<props and values>}
 | ||||||
|  |   // O:8:"stdClass":2:{s:3:"foo";s:3:"bar";s:3:"bar";s:3:"baz";}
 | ||||||
|  |   const reObjectLiteral = /^O:(\d+):"([^"]+)":(\d+):\{/ | ||||||
|  |   const [ objectLiteralBeginMatch, /* classNameLengthMatch */, className, propCountMatch ] = reObjectLiteral.exec(str) || [] | ||||||
|  | 
 | ||||||
|  |   if (!objectLiteralBeginMatch) { | ||||||
|  |     throw SyntaxError('Invalid input') | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (className !== 'stdClass') { | ||||||
|  |     throw SyntaxError(`Unsupported object type: ${className}`) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   let totalOffset = objectLiteralBeginMatch.length | ||||||
|  | 
 | ||||||
|  |   const propCount = parseInt(propCountMatch, 10) | ||||||
|  |   const obj = {} | ||||||
|  |   cache([obj]) | ||||||
|  | 
 | ||||||
|  |   str = str.substr(totalOffset) | ||||||
|  | 
 | ||||||
|  |   for (let i = 0; i < propCount; i++) { | ||||||
|  |     const prop = expectKeyOrIndex(str) | ||||||
|  |     str = str.substr(prop[1]) | ||||||
|  |     totalOffset += prop[1] as number | ||||||
|  | 
 | ||||||
|  |     const value = expectType(str, cache) | ||||||
|  |     str = str.substr(value[1]) | ||||||
|  |     totalOffset += value[1] | ||||||
|  | 
 | ||||||
|  |     obj[prop[0]] = value[0] | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // strict parsing, expect } after object literal
 | ||||||
|  |   if (str.charAt(0) !== '}') { | ||||||
|  |     throw SyntaxError('Expected }') | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return [ obj, totalOffset + 1 ] // skip final }
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function expectClass (str, cache) { | ||||||
|  |   // can't be well supported, because requires calling eval (or similar)
 | ||||||
|  |   // in order to call serialized constructor name
 | ||||||
|  |   // which is unsafe
 | ||||||
|  |   // or assume that constructor is defined in global scope
 | ||||||
|  |   // but this is too much limiting
 | ||||||
|  |   throw Error('Not yet implemented') | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function expectReference (str, cache) { | ||||||
|  |   const reRef = /^[rR]:([1-9]\d*);/ | ||||||
|  |   const [ match, refIndex ] = reRef.exec(str) || [] | ||||||
|  | 
 | ||||||
|  |   if (!match) { | ||||||
|  |     throw SyntaxError('Expected reference value') | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return [ cache.get(parseInt(refIndex, 10) - 1), match.length ] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function expectArray (str, cache) { | ||||||
|  |   const reArrayLength = /^a:(\d+):{/ | ||||||
|  |   const [ arrayLiteralBeginMatch, arrayLengthMatch ] = reArrayLength.exec(str) || [] | ||||||
|  | 
 | ||||||
|  |   if (!arrayLengthMatch) { | ||||||
|  |     throw SyntaxError('Expected array length annotation') | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   str = str.substr(arrayLiteralBeginMatch.length) | ||||||
|  | 
 | ||||||
|  |   const array = expectArrayItems(str, parseInt(arrayLengthMatch, 10), cache) | ||||||
|  | 
 | ||||||
|  |   // strict parsing, expect closing } brace after array literal
 | ||||||
|  |   if (str.charAt(array[1]) !== '}') { | ||||||
|  |     throw SyntaxError('Expected }') | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return [ array[0], arrayLiteralBeginMatch.length + (array[1] as number) + 1 ] // jump over }
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function expectArrayItems (str, expectedItems = 0, cache) { | ||||||
|  |   let key | ||||||
|  |   let hasStringKeys = false | ||||||
|  |   let item | ||||||
|  |   let totalOffset = 0 | ||||||
|  |   let items = [] | ||||||
|  |   cache([items]) | ||||||
|  | 
 | ||||||
|  |   for (let i = 0; i < expectedItems; i++) { | ||||||
|  |     key = expectKeyOrIndex(str) | ||||||
|  | 
 | ||||||
|  |     // this is for backward compatibility with previous implementation
 | ||||||
|  |     if (!hasStringKeys) { | ||||||
|  |       hasStringKeys = (typeof key[0] === 'string') | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     str = str.substr(key[1]) | ||||||
|  |     totalOffset += key[1] | ||||||
|  | 
 | ||||||
|  |     // references are resolved immediately, so if duplicate key overwrites previous array index
 | ||||||
|  |     // the old value is anyway resolved
 | ||||||
|  |     // fixme: but next time the same reference should point to the new value
 | ||||||
|  |     item = expectType(str, cache) | ||||||
|  |     str = str.substr(item[1]) | ||||||
|  |     totalOffset += item[1] | ||||||
|  | 
 | ||||||
|  |     items[key[0]] = item[0] | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // this is for backward compatibility with previous implementation
 | ||||||
|  |   if (hasStringKeys) { | ||||||
|  |     items = Object.assign({}, items) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return [ items, totalOffset ] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function unserialize (str) { | ||||||
|  |   //       discuss at: https://locutus.io/php/unserialize/
 | ||||||
|  |   //      original by: Arpad Ray (mailto:arpad@php.net)
 | ||||||
|  |   //      improved by: Pedro Tainha (https://www.pedrotainha.com)
 | ||||||
|  |   //      improved by: Kevin van Zonneveld (https://kvz.io)
 | ||||||
|  |   //      improved by: Kevin van Zonneveld (https://kvz.io)
 | ||||||
|  |   //      improved by: Chris
 | ||||||
|  |   //      improved by: James
 | ||||||
|  |   //      improved by: Le Torbi
 | ||||||
|  |   //      improved by: Eli Skeggs
 | ||||||
|  |   //      bugfixed by: dptr1988
 | ||||||
|  |   //      bugfixed by: Kevin van Zonneveld (https://kvz.io)
 | ||||||
|  |   //      bugfixed by: Brett Zamir (https://brett-zamir.me)
 | ||||||
|  |   //      bugfixed by: philippsimon (https://github.com/philippsimon/)
 | ||||||
|  |   //       revised by: d3x
 | ||||||
|  |   //         input by: Brett Zamir (https://brett-zamir.me)
 | ||||||
|  |   //         input by: Martin (https://www.erlenwiese.de/)
 | ||||||
|  |   //         input by: kilops
 | ||||||
|  |   //         input by: Jaroslaw Czarniak
 | ||||||
|  |   //         input by: lovasoa (https://github.com/lovasoa/)
 | ||||||
|  |   //      improved by: Rafał Kukawski
 | ||||||
|  |   // reimplemented by: Rafał Kukawski
 | ||||||
|  |   //           note 1: We feel the main purpose of this function should be
 | ||||||
|  |   //           note 1: to ease the transport of data between php & js
 | ||||||
|  |   //           note 1: Aiming for PHP-compatibility, we have to translate objects to arrays
 | ||||||
|  |   //        example 1: unserialize('a:3:{i:0;s:5:"Kevin";i:1;s:3:"van";i:2;s:9:"Zonneveld";}')
 | ||||||
|  |   //        returns 1: ['Kevin', 'van', 'Zonneveld']
 | ||||||
|  |   //        example 2: unserialize('a:2:{s:9:"firstName";s:5:"Kevin";s:7:"midName";s:3:"van";}')
 | ||||||
|  |   //        returns 2: {firstName: 'Kevin', midName: 'van'}
 | ||||||
|  |   //        example 3: unserialize('a:3:{s:2:"ü";s:2:"ü";s:3:"四";s:3:"四";s:4:"𠜎";s:4:"𠜎";}')
 | ||||||
|  |   //        returns 3: {'ü': 'ü', '四': '四', '𠜎': '𠜎'}
 | ||||||
|  |   //        example 4: unserialize(undefined)
 | ||||||
|  |   //        returns 4: false
 | ||||||
|  |   //        example 5: unserialize('O:8:"stdClass":1:{s:3:"foo";b:1;}')
 | ||||||
|  |   //        returns 5: { foo: true }
 | ||||||
|  |   //        example 6: unserialize('a:2:{i:0;N;i:1;s:0:"";}')
 | ||||||
|  |   //        returns 6: [null, ""]
 | ||||||
|  |   //        example 7: unserialize('S:7:"\\65\\73\\63\\61\\70\\65\\64";')
 | ||||||
|  |   //        returns 7: 'escaped'
 | ||||||
|  | 
 | ||||||
|  |   try { | ||||||
|  |     if (typeof str !== 'string') { | ||||||
|  |       return false | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return expectType(str, initCache())[0] | ||||||
|  |   } catch (err) { | ||||||
|  |     console.error(err) | ||||||
|  |     return false | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function substr_replace (str, replace, start, length) { // eslint-disable-line camelcase
 | ||||||
|  |   //  discuss at: https://locutus.io/php/substr_replace/
 | ||||||
|  |   // original by: Brett Zamir (https://brett-zamir.me)
 | ||||||
|  |   //   example 1: substr_replace('ABCDEFGH:/MNRPQR/', 'bob', 0)
 | ||||||
|  |   //   returns 1: 'bob'
 | ||||||
|  |   //   example 2: var $var = 'ABCDEFGH:/MNRPQR/'
 | ||||||
|  |   //   example 2: substr_replace($var, 'bob', 0, $var.length)
 | ||||||
|  |   //   returns 2: 'bob'
 | ||||||
|  |   //   example 3: substr_replace('ABCDEFGH:/MNRPQR/', 'bob', 0, 0)
 | ||||||
|  |   //   returns 3: 'bobABCDEFGH:/MNRPQR/'
 | ||||||
|  |   //   example 4: substr_replace('ABCDEFGH:/MNRPQR/', 'bob', 10, -1)
 | ||||||
|  |   //   returns 4: 'ABCDEFGH:/bob/'
 | ||||||
|  |   //   example 5: substr_replace('ABCDEFGH:/MNRPQR/', 'bob', -7, -1)
 | ||||||
|  |   //   returns 5: 'ABCDEFGH:/bob/'
 | ||||||
|  |   //   example 6: substr_replace('ABCDEFGH:/MNRPQR/', '', 10, -1)
 | ||||||
|  |   //   returns 6: 'ABCDEFGH://'
 | ||||||
|  | 
 | ||||||
|  |   if (start < 0) { | ||||||
|  |     // start position in str
 | ||||||
|  |     start = start + str.length | ||||||
|  |   } | ||||||
|  |   length = length !== undefined ? length : str.length | ||||||
|  |   if (length < 0) { | ||||||
|  |     length = length + str.length - start | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return [ | ||||||
|  |     str.slice(0, start), | ||||||
|  |     replace.substr(0, length), | ||||||
|  |     replace.slice(length), | ||||||
|  |     str.slice(start + length) | ||||||
|  |   ].join('') | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class Locutus { | ||||||
|  | 
 | ||||||
|  |     static unserialize<T = unknown>(data: string): T { | ||||||
|  |         return unserialize(data); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     static substrReplace(str: string, replace: string, start: number, length?: number): string { | ||||||
|  |         return substr_replace(str, replace, start, length); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | }; | ||||||
							
								
								
									
										1272
									
								
								src/assets/exttomime.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1272
									
								
								src/assets/exttomime.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										1095
									
								
								src/assets/mimetoext.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1095
									
								
								src/assets/mimetoext.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -3,8 +3,9 @@ | |||||||
|   "compilerOptions": { |   "compilerOptions": { | ||||||
|     "outDir": "./out-tsc/app", |     "outDir": "./out-tsc/app", | ||||||
|     "types": [ |     "types": [ | ||||||
|       "cordova", |  | ||||||
|       "cordova-plugin-file-transfer", |       "cordova-plugin-file-transfer", | ||||||
|  |       "cordova-plugin-inappbrowser", | ||||||
|  |       "cordova", | ||||||
|       "node" |       "node" | ||||||
|     ], |     ], | ||||||
|     "paths": { |     "paths": { | ||||||
|  | |||||||
| @ -18,6 +18,9 @@ | |||||||
|       "dom" |       "dom" | ||||||
|     ], |     ], | ||||||
|     "types": [ |     "types": [ | ||||||
|  |         "cordova-plugin-file-transfer", | ||||||
|  |         "cordova-plugin-inappbrowser", | ||||||
|  |         "cordova", | ||||||
|         "jest", |         "jest", | ||||||
|         "node" |         "node" | ||||||
|     ], |     ], | ||||||
|  | |||||||
| @ -6,6 +6,9 @@ | |||||||
|     "emitDecoratorMetadata": true, |     "emitDecoratorMetadata": true, | ||||||
|     "outDir": "./out-tsc/tests", |     "outDir": "./out-tsc/tests", | ||||||
|     "types": [ |     "types": [ | ||||||
|  |       "cordova-plugin-file-transfer", | ||||||
|  |       "cordova-plugin-inappbrowser", | ||||||
|  |       "cordova", | ||||||
|       "jest", |       "jest", | ||||||
|       "node" |       "node" | ||||||
|     ], |     ], | ||||||
|  | |||||||
							
								
								
									
										144
									
								
								tslint.json
									
									
									
									
									
								
							
							
						
						
									
										144
									
								
								tslint.json
									
									
									
									
									
								
							| @ -1,144 +0,0 @@ | |||||||
| { |  | ||||||
|   "extends": "tslint:recommended", |  | ||||||
|   "rules": { |  | ||||||
|     "array-type": false, |  | ||||||
|     "arrow-return-shorthand": true, |  | ||||||
|     "curly": true, |  | ||||||
|     "deprecation": { |  | ||||||
|       "severity": "warning" |  | ||||||
|     }, |  | ||||||
|     "component-class-suffix": [true, "Page", "Component"], |  | ||||||
|     "contextual-lifecycle": true, |  | ||||||
|     "directive-class-suffix": true, |  | ||||||
|     "directive-selector": [ |  | ||||||
|       true, |  | ||||||
|       "attribute", |  | ||||||
|       "app", |  | ||||||
|       "camelCase" |  | ||||||
|     ], |  | ||||||
|     "eofline": true, |  | ||||||
|     "import-blacklist": [ |  | ||||||
|       true, |  | ||||||
|       "rxjs/Rx" |  | ||||||
|     ], |  | ||||||
|     "import-spacing": true, |  | ||||||
|     "indent": { |  | ||||||
|       "options": [ |  | ||||||
|         "spaces" |  | ||||||
|       ] |  | ||||||
|     }, |  | ||||||
|     "max-classes-per-file": false, |  | ||||||
|     "max-line-length": [ |  | ||||||
|       true, |  | ||||||
|       140 |  | ||||||
|     ], |  | ||||||
|     "member-ordering": [ |  | ||||||
|       true, |  | ||||||
|       { |  | ||||||
|         "order": [ |  | ||||||
|           "static-field", |  | ||||||
|           "instance-field", |  | ||||||
|           "static-method", |  | ||||||
|           "instance-method" |  | ||||||
|         ] |  | ||||||
|       } |  | ||||||
|     ], |  | ||||||
|     "no-angle-bracket-type-assertion": false, |  | ||||||
|     "no-console": [ |  | ||||||
|       true, |  | ||||||
|       "debug", |  | ||||||
|       "info", |  | ||||||
|       "time", |  | ||||||
|       "timeEnd", |  | ||||||
|       "trace" |  | ||||||
|     ], |  | ||||||
|     "no-empty": false, |  | ||||||
|     "no-inferrable-types": [ |  | ||||||
|       true, |  | ||||||
|       "ignore-params" |  | ||||||
|     ], |  | ||||||
|     "no-non-null-assertion": true, |  | ||||||
|     "no-redundant-jsdoc": true, |  | ||||||
|     "no-shadowed-variable": false, |  | ||||||
|     "no-switch-case-fall-through": true, |  | ||||||
|     "no-unused-expression": false, |  | ||||||
|     "no-var-requires": false, |  | ||||||
|     "object-literal-key-quotes": [ |  | ||||||
|       true, |  | ||||||
|       "as-needed" |  | ||||||
|     ], |  | ||||||
|     "prefer-for-of": false, |  | ||||||
|     "quotemark": [ |  | ||||||
|       true, |  | ||||||
|       "single" |  | ||||||
|     ], |  | ||||||
|     "semicolon": { |  | ||||||
|       "options": [ |  | ||||||
|         "always" |  | ||||||
|       ] |  | ||||||
|     }, |  | ||||||
|     "space-before-function-paren": { |  | ||||||
|       "options": { |  | ||||||
|         "anonymous": "never", |  | ||||||
|         "asyncArrow": "always", |  | ||||||
|         "constructor": "never", |  | ||||||
|         "method": "never", |  | ||||||
|         "named": "never" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "typedef-whitespace": { |  | ||||||
|       "options": [ |  | ||||||
|         { |  | ||||||
|           "call-signature": "nospace", |  | ||||||
|           "index-signature": "nospace", |  | ||||||
|           "parameter": "nospace", |  | ||||||
|           "property-declaration": "nospace", |  | ||||||
|           "variable-declaration": "nospace" |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           "call-signature": "onespace", |  | ||||||
|           "index-signature": "onespace", |  | ||||||
|           "parameter": "onespace", |  | ||||||
|           "property-declaration": "onespace", |  | ||||||
|           "variable-declaration": "onespace" |  | ||||||
|         } |  | ||||||
|       ] |  | ||||||
|     }, |  | ||||||
|     "variable-name": { |  | ||||||
|       "options": [ |  | ||||||
|         "ban-keywords", |  | ||||||
|         "check-format", |  | ||||||
|         "allow-pascal-case", |  | ||||||
|         "allow-leading-underscore" |  | ||||||
|       ] |  | ||||||
|     }, |  | ||||||
|     "whitespace": { |  | ||||||
|       "options": [ |  | ||||||
|         "check-branch", |  | ||||||
|         "check-decl", |  | ||||||
|         "check-operator", |  | ||||||
|         "check-separator", |  | ||||||
|         "check-type", |  | ||||||
|         "check-typecast" |  | ||||||
|       ] |  | ||||||
|     }, |  | ||||||
|     "no-conflicting-lifecycle": true, |  | ||||||
|     "no-host-metadata-property": true, |  | ||||||
|     "no-input-rename": true, |  | ||||||
|     "no-inputs-metadata-property": true, |  | ||||||
|     "no-output-native": true, |  | ||||||
|     "no-output-on-prefix": true, |  | ||||||
|     "no-output-rename": true, |  | ||||||
|     "no-outputs-metadata-property": true, |  | ||||||
|     "template-banana-in-box": true, |  | ||||||
|     "template-no-negated-async": true, |  | ||||||
|     "use-lifecycle-interface": true, |  | ||||||
|     "use-pipe-transform-interface": true, |  | ||||||
|     "object-literal-sort-keys": false, |  | ||||||
|     "forin": false, |  | ||||||
|     "triple-equals": false |  | ||||||
|   }, |  | ||||||
|   "rulesDirectory": [ |  | ||||||
|     "codelyzer" |  | ||||||
|   ] |  | ||||||
| } |  | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user