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 @@
@@ -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.
*