diff --git a/src/addons/block/myoverview/components/myoverview/myoverview.ts b/src/addons/block/myoverview/components/myoverview/myoverview.ts index e31ff0d1d..bbf812037 100644 --- a/src/addons/block/myoverview/components/myoverview/myoverview.ts +++ b/src/addons/block/myoverview/components/myoverview/myoverview.ts @@ -176,6 +176,8 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem * @inheritdoc */ ngOnChanges(changes: SimpleChanges): void { + super.ngOnChanges(changes); + if (this.loaded && changes.block) { // Block was re-fetched, load content. this.reloadContent(); diff --git a/src/addons/mod/assign/components/submission/submission.ts b/src/addons/mod/assign/components/submission/submission.ts index 7305092ad..4bf833519 100644 --- a/src/addons/mod/assign/components/submission/submission.ts +++ b/src/addons/mod/assign/components/submission/submission.ts @@ -1089,11 +1089,11 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can // Only show outcomes with info on it, outcomeid could be null if outcomes are disabled on site. gradeInfo.outcomes?.forEach((outcome) => { if (outcome.id == String(grade.outcomeid)) { - outcome.selected = grade.gradeformatted; + // Clean HTML tags, grade can contain an icon. + outcome.selected = CoreTextUtils.cleanTags(grade.gradeformatted || ''); outcome.modified = grade.gradedategraded; if (outcome.options) { - outcome.selectedId = - CoreGradesHelper.getGradeValueFromLabel(outcome.options, outcome.selected || ''); + outcome.selectedId = CoreGradesHelper.getGradeValueFromLabel(outcome.options, outcome.selected); this.originalGrades.outcomes[outcome.id] = outcome.selectedId; outcome.itemNumber = grade.itemnumber; } diff --git a/src/core/components/recaptcha/core-recaptcha.html b/src/core/components/recaptcha/core-recaptcha.html index 102ddefc2..3e96f751d 100644 --- a/src/core/components/recaptcha/core-recaptcha.html +++ b/src/core/components/recaptcha/core-recaptcha.html @@ -7,7 +7,10 @@ {{ 'core.answered' | translate }} - + {{ 'core.login.recaptchaexpired' | translate }} + + {{ 'core.required' | translate }} + diff --git a/src/core/components/recaptcha/recaptcha.ts b/src/core/components/recaptcha/recaptcha.ts index fb6e1f260..ef4608f4a 100644 --- a/src/core/components/recaptcha/recaptcha.ts +++ b/src/core/components/recaptcha/recaptcha.ts @@ -32,6 +32,7 @@ export class CoreRecaptchaComponent implements OnInit { @Input() publicKey?: string; // The site public key. @Input() modelValueName = 'recaptcharesponse'; // Name of the model property where to store the response. @Input() siteUrl = ''; // The site URL. If not defined, current site. + @Input() showRequiredError = false; // Whether to display the required error if recaptcha hasn't been answered. expired = false; diff --git a/src/core/features/block/classes/base-block-component.ts b/src/core/features/block/classes/base-block-component.ts index c6073cac6..05bec595c 100644 --- a/src/core/features/block/classes/base-block-component.ts +++ b/src/core/features/block/classes/base-block-component.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { OnInit, Input, Component, Optional, Inject } from '@angular/core'; +import { OnInit, Input, Component, Optional, Inject, OnChanges, SimpleChanges } from '@angular/core'; import { CoreLogger } from '@singletons/logger'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreUtils } from '@services/utils/utils'; @@ -30,7 +30,7 @@ import { CorePromisedValue } from '@classes/promised-value'; @Component({ template: '', }) -export abstract class CoreBlockBaseComponent implements OnInit, ICoreBlockComponent, AsyncDirective { +export abstract class CoreBlockBaseComponent implements OnInit, OnChanges, ICoreBlockComponent, AsyncDirective { @Input() title!: string; // The block title. @Input() block!: CoreCourseBlock; // The block to render. @@ -54,15 +54,31 @@ export abstract class CoreBlockBaseComponent implements OnInit, ICoreBlockCompon * @inheritdoc */ async ngOnInit(): Promise { - if (this.block.configs && this.block.configs.length > 0) { - this.block.configs.forEach((config) => { - config.value = CoreTextUtils.parseJSON(config.value); - }); + await this.loadContent(); + } - this.block.configsRecord = CoreUtils.arrayToObject(this.block.configs, 'name'); + /** + * @inheritdoc + */ + ngOnChanges(changes: SimpleChanges): void { + if (changes.block) { + this.parseConfigs(); + } + } + + /** + * Parse configs if needed. + */ + protected parseConfigs(): void { + if (!this.block.configs?.length || this.block.configsRecord) { + return; } - await this.loadContent(); + this.block.configs.forEach((config) => { + config.value = CoreTextUtils.parseJSON(config.value); + }); + + this.block.configsRecord = CoreUtils.arrayToObject(this.block.configs, 'name'); } /** diff --git a/src/core/features/login/pages/credentials/credentials.ts b/src/core/features/login/pages/credentials/credentials.ts index e6b995c06..7039451f3 100644 --- a/src/core/features/login/pages/credentials/credentials.ts +++ b/src/core/features/login/pages/credentials/credentials.ts @@ -193,7 +193,6 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy { this.siteName = this.siteConfig.sitename; this.logoUrl = CoreLoginHelper.getLogoUrl(this.siteConfig); - this.authInstructions = this.siteConfig.authinstructions || Translate.instant('core.login.loginsteps'); this.showScanQR = await CoreLoginHelper.displayQRInCredentialsScreen(this.siteConfig.tool_mobile_qrcodetype); const disabledFeatures = CoreLoginHelper.getDisabledFeatures(this.siteConfig); @@ -204,6 +203,8 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy { !!this.supportConfig?.canContactSupport(), this.showForgottenPassword, ); + this.authInstructions = this.siteConfig.authinstructions || + (this.canSignup ? Translate.instant('core.login.loginsteps') : ''); if (!this.eventThrown && !this.viewLeft) { this.eventThrown = true; diff --git a/src/core/features/login/pages/email-signup/email-signup.html b/src/core/features/login/pages/email-signup/email-signup.html index b1bafd6f5..e7d5c0b7d 100644 --- a/src/core/features/login/pages/email-signup/email-signup.html +++ b/src/core/features/login/pages/email-signup/email-signup.html @@ -21,13 +21,13 @@
- + {{ 'core.login.signuprequiredfieldnotsupported' | translate }} - + {{ 'core.openinbrowser' | translate }} @@ -62,9 +62,11 @@ - - {{ 'core.proceed' | translate }} - +
+ + {{ 'core.proceed' | translate }} + +
@@ -186,7 +188,8 @@

{{ 'core.login.security_question' | translate }}

- + @@ -213,10 +216,12 @@
- - {{ 'core.login.createaccount' | translate }} - - +
+ + {{ 'core.login.createaccount' | translate }} + + +
@@ -239,9 +244,11 @@

{{ supportEmail }}

- - {{ 'core.openinbrowser' | translate }} - +
+ + {{ 'core.openinbrowser' | translate }} + +
diff --git a/src/core/features/login/pages/email-signup/email-signup.ts b/src/core/features/login/pages/email-signup/email-signup.ts index 253d20fdc..f0d0f3f3f 100644 --- a/src/core/features/login/pages/email-signup/email-signup.ts +++ b/src/core/features/login/pages/email-signup/email-signup.ts @@ -60,6 +60,7 @@ export class CoreLoginEmailSignupPage implements OnInit { settingsLoaded = false; allRequiredSupported = true; signupUrl?: string; + formSubmitClicked = false; captcha = { recaptcharesponse: '', }; @@ -265,6 +266,8 @@ export class CoreLoginEmailSignupPage implements OnInit { e.preventDefault(); e.stopPropagation(); + this.formSubmitClicked = true; + if (!this.signupForm.valid || (this.settings?.recaptchapublickey && !this.captcha.recaptcharesponse)) { // Form not valid. Mark all controls as dirty to display errors. for (const name in this.signupForm.controls) { diff --git a/src/core/features/login/tests/behat/snapshots/test-basic-usage-of-login-in-app-add-a-new-account-in-the-app--site-name-in-displayed-when-adding-a-new-account_9.png b/src/core/features/login/tests/behat/snapshots/test-basic-usage-of-login-in-app-add-a-new-account-in-the-app--site-name-in-displayed-when-adding-a-new-account_9.png index 727cbb0e1..6abd7693a 100644 Binary files a/src/core/features/login/tests/behat/snapshots/test-basic-usage-of-login-in-app-add-a-new-account-in-the-app--site-name-in-displayed-when-adding-a-new-account_9.png and b/src/core/features/login/tests/behat/snapshots/test-basic-usage-of-login-in-app-add-a-new-account-in-the-app--site-name-in-displayed-when-adding-a-new-account_9.png differ diff --git a/src/core/features/settings/pages/general/general.html b/src/core/features/settings/pages/general/general.html index feb5e33a3..57fe64eee 100644 --- a/src/core/features/settings/pages/general/general.html +++ b/src/core/features/settings/pages/general/general.html @@ -75,7 +75,7 @@ - +

{{ 'core.settings.enableanalytics' | translate }}

{{ 'core.settings.enableanalyticsdescription' | translate }}

diff --git a/src/core/features/settings/pages/general/general.ts b/src/core/features/settings/pages/general/general.ts index de859cb2d..638b088ef 100644 --- a/src/core/features/settings/pages/general/general.ts +++ b/src/core/features/settings/pages/general/general.ts @@ -44,7 +44,7 @@ export class CoreSettingsGeneralPage { selectedZoomLevel = CoreZoomLevel.NONE; richTextEditor = true; debugDisplay = false; - analyticsSupported = false; + analyticsAvailable = false; analyticsEnabled = false; colorSchemes: CoreColorScheme[] = []; selectedScheme: CoreColorScheme = CoreColorScheme.LIGHT; @@ -101,8 +101,8 @@ export class CoreSettingsGeneralPage { this.debugDisplay = await CoreConfig.get(CoreConstants.SETTINGS_DEBUG_DISPLAY, false); - this.analyticsSupported = CoreAnalytics.hasHandlers(); - if (this.analyticsSupported) { + this.analyticsAvailable = await CoreAnalytics.isAnalyticsAvailable(); + if (this.analyticsAvailable) { this.analyticsEnabled = await CoreConfig.get(CoreConstants.SETTINGS_ANALYTICS_ENABLED, true); } diff --git a/src/core/services/analytics.ts b/src/core/services/analytics.ts index e442a6a89..71146ad89 100644 --- a/src/core/services/analytics.ts +++ b/src/core/services/analytics.ts @@ -57,6 +57,29 @@ export class CoreAnalyticsService extends CoreDelegate { } } + /** + * Check if analytics is available for the app/site. + * + * @returns True if available, false otherwise. + */ + async isAnalyticsAvailable(): Promise { + if (Object.keys(this.enabledHandlers).length > 0) { + // There is an enabled handler, analytics is available. + return true; + } + + // Check if there is a handler that is enabled at app level (enabled handlers are only set when logged in). + const enabledList = await Promise.all(Object.values(this.handlers).map(handler => { + if (!handler.appLevelEnabled) { + return false; + } + + return handler.isEnabled(); + })); + + return enabledList.includes(true); + } + /** * Log an event for the current site. * @@ -108,6 +131,11 @@ export const CoreAnalytics = makeSingleton(CoreAnalyticsService); */ export interface CoreAnalyticsHandler extends CoreDelegateHandler { + /** + * If true it means that the handler is enabled or not for the whole app, it doesn't depend on the site. + */ + appLevelEnabled?: boolean; + /** * Log an event. *