MOBILE-3649 mod: Make module and courseId required in index components

main
Dani Palou 2021-03-12 10:09:38 +01:00
parent 3dc1834a6b
commit 2a077f4e62
21 changed files with 133 additions and 148 deletions

View File

@ -35,7 +35,7 @@
<ion-item class="ion-text-wrap">
<ion-label>
<core-format-text [text]="description" [component]="component" [componentId]="componentId" maxHeight="120"
contextLevel="module" [contextInstanceId]="module!.id" [courseId]="courseId" (click)="expandDescription($event)">
contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId" (click)="expandDescription($event)">
</core-format-text>
</ion-label>
</ion-item>
@ -136,7 +136,7 @@
<!-- If it's a student, display his submission. -->
<addon-mod-assign-submission *ngIf="loaded && !canViewAllSubmissions && canViewOwnSubmission" [courseId]="courseId"
[moduleId]="module!.id">
[moduleId]="module.id">
</addon-mod-assign-submission>
</core-loading>

View File

@ -120,7 +120,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
(data) => {
if (this.assign && data.assignmentId == this.assign.id && data.userId == this.currentUserId) {
// Assignment submitted, check completion.
CoreCourse.checkModuleCompletion(this.courseId!, this.module!.completiondata);
CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata);
// Reload data since it can have offline data now.
this.showLoadingAndRefresh(true, false);
@ -140,7 +140,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
try {
await AddonModAssign.logView(this.assign!.id, this.assign!.name);
CoreCourse.checkModuleCompletion(this.courseId!, this.module!.completiondata);
CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata);
} catch {
// Ignore errors. Just don't check Module completion.
}
@ -164,11 +164,11 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
if (this.assign && (this.description || this.assign.introattachments)) {
CoreTextUtils.viewText(Translate.instant('core.description'), this.description || '', {
component: this.component,
componentId: this.module!.id,
componentId: this.module.id,
files: this.assign.introattachments,
filter: true,
contextLevel: 'module',
instanceId: this.module!.id,
instanceId: this.module.id,
courseId: this.courseId,
});
}
@ -186,7 +186,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
// Get assignment data.
try {
this.assign = await AddonModAssign.getAssignment(this.courseId!, this.module!.id);
this.assign = await AddonModAssign.getAssignment(this.courseId, this.module.id);
this.dataRetrieved.emit(this.assign);
this.description = this.assign.intro;
@ -200,7 +200,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
this.hasOffline = await AddonModAssignOffline.hasAssignOfflineData(this.assign.id);
// Get assignment submissions.
const submissions = await AddonModAssign.getSubmissions(this.assign.id, { cmId: this.module!.id });
const submissions = await AddonModAssign.getSubmissions(this.assign.id, { cmId: this.module.id });
const time = CoreTimeUtils.timestamp();
this.canViewAllSubmissions = submissions.canviewsubmissions;
@ -244,7 +244,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
try {
// Check if the user can view their own submission.
await AddonModAssign.getSubmissionStatus(this.assign.id, { cmId: this.module!.id });
await AddonModAssign.getSubmissionStatus(this.assign.id, { cmId: this.module.id });
this.canViewOwnSubmission = true;
} catch (error) {
this.canViewOwnSubmission = false;
@ -269,7 +269,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
const submissionStatus = await AddonModAssign.getSubmissionStatus(this.assign!.id, {
groupId: this.group,
cmId: this.module!.id,
cmId: this.module.id,
});
this.summary = submissionStatus.gradingsummary;
@ -345,7 +345,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
protected async invalidateContent(): Promise<void> {
const promises: Promise<void>[] = [];
promises.push(AddonModAssign.invalidateAssignmentData(this.courseId!));
promises.push(AddonModAssign.invalidateAssignmentData(this.courseId));
if (this.assign) {
promises.push(AddonModAssign.invalidateAllSubmissionData(this.assign.id));

View File

@ -24,7 +24,7 @@
<core-loading [hideUntil]="loaded" class="core-loading-center">
<core-course-module-description [description]="description" [component]="component" [componentId]="componentId"
contextLevel="module" [contextInstanceId]="module?.id" [courseId]="courseId"></core-course-module-description>
contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"></core-course-module-description>
<ion-card class="core-warning-card" *ngIf="warning">
<ion-icon name="fas-exclamation-triangle" slot="start"></ion-icon>
@ -38,7 +38,7 @@
</core-navigation-bar>
<core-format-text [component]="component" [componentId]="componentId" [text]="chapterContent" contextLevel="module"
[contextInstanceId]="module?.id" [courseId]="courseId"></core-format-text>
[contextInstanceId]="module.id" [courseId]="courseId"></core-format-text>
<div class="ion-margin-top" *ngIf="tagsEnabled && tags?.length > 0">
<strong>{{ 'core.tag.tags' | translate }}: </strong>
<core-tag-list [tags]="tags"></core-tag-list>

View File

@ -87,7 +87,7 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp
const modal = await ModalController.create({
component: AddonModBookTocComponent,
componentProps: {
moduleId: this.module!.id,
moduleId: this.module.id,
chapters: this.chapters,
selected: this.currentChapter,
courseId: this.courseId,
@ -129,7 +129,7 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp
* @return Resolved when done.
*/
protected invalidateContent(): Promise<void> {
return AddonModBook.invalidateContent(this.module!.id, this.courseId!);
return AddonModBook.invalidateContent(this.module.id, this.courseId);
}
/**
@ -143,7 +143,7 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp
let downloadResult: CoreCourseResourceDownloadResult | undefined;
// Try to get the book data. Ignore errors since this WS isn't available in some Moodle versions.
promises.push(CoreUtils.ignoreErrors(AddonModBook.getBook(this.courseId!, this.module!.id))
promises.push(CoreUtils.ignoreErrors(AddonModBook.getBook(this.courseId, this.module.id))
.then((book) => {
if (!book) {
return;
@ -169,8 +169,8 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp
try {
await Promise.all(promises);
this.contentsMap = AddonModBook.getContentsMap(this.module!.contents);
this.chapters = AddonModBook.getTocList(this.module!.contents);
this.contentsMap = AddonModBook.getContentsMap(this.module.contents);
this.chapters = AddonModBook.getTocList(this.module.contents);
if (typeof this.currentChapter == 'undefined' && typeof this.initialChapterId != 'undefined' && this.chapters) {
// Initial chapter set. Validate that the chapter exists.
@ -211,7 +211,7 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp
this.content?.scrollToTop();
try {
const content = await AddonModBook.getChapterContent(this.contentsMap, chapterId, this.module!.id);
const content = await AddonModBook.getChapterContent(this.contentsMap, chapterId, this.module.id);
this.tags = this.tagsEnabled ? this.contentsMap[this.currentChapter].tags : [];
@ -228,14 +228,14 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp
// Chapter loaded, log view. We don't return the promise because we don't want to block the user for this.
await CoreUtils.ignoreErrors(AddonModBook.logView(
this.module!.instance!,
this.module.instance!,
logChapterId ? chapterId : undefined,
this.module!.name,
this.module.name,
));
// Module is completed when last chapter is viewed, so we only check completion if the last is reached.
if (!this.nextChapter) {
CoreCourse.checkModuleCompletion(this.courseId!, this.module!.completiondata);
CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata);
}
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'addon.mod_book.errorchapter', true);

View File

@ -26,7 +26,7 @@
<core-loading [hideUntil]="loaded" class="core-loading-center">
<core-course-module-description [description]="description" [component]="component" [componentId]="componentId"
contextLevel="module" [contextInstanceId]="module?.id" [courseId]="courseId">
contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
</core-course-module-description>
<ion-list *ngIf="subfolder && (subfolder!.files.length + subfolder!.folders.length > 0)">

View File

@ -55,7 +55,7 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo
this.canGetFolder = AddonModFolder.isGetFolderWSAvailable();
if (this.subfolder) {
this.description = this.folderInstance ? this.folderInstance.intro : this.module!.description;
this.description = this.folderInstance ? this.folderInstance.intro : this.module.description;
this.loaded = true;
this.refreshIcon = CoreConstants.ICON_REFRESH;
@ -67,8 +67,8 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo
await this.loadContent();
try {
await AddonModFolder.logView(this.module!.instance!, this.module!.name);
CoreCourse.checkModuleCompletion(this.courseId!, this.module!.completiondata);
await AddonModFolder.logView(this.module.instance!, this.module.name);
CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata);
} catch {
// Ignore errors.
}
@ -84,7 +84,7 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo
* @return Resolved when done.
*/
protected async invalidateContent(): Promise<void> {
await AddonModFolder.invalidateContent(this.module!.id, this.courseId!);
await AddonModFolder.invalidateContent(this.module.id, this.courseId);
}
/**
@ -96,22 +96,22 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo
protected async fetchContent(refresh = false): Promise<void> {
try {
if (this.canGetFolder) {
this.folderInstance = await AddonModFolder.getFolder(this.courseId!, this.module!.id);
await CoreCourse.loadModuleContents(this.module!, this.courseId, undefined, false, refresh);
this.folderInstance = await AddonModFolder.getFolder(this.courseId, this.module.id);
await CoreCourse.loadModuleContents(this.module, this.courseId, undefined, false, refresh);
} else {
const module = await CoreCourse.getModule(this.module!.id, this.courseId);
const module = await CoreCourse.getModule(this.module.id, this.courseId);
if (!module.contents.length && this.module!.contents.length && !CoreApp.isOnline()) {
if (!module.contents.length && this.module.contents.length && !CoreApp.isOnline()) {
// The contents might be empty due to a cached data. Use the old ones.
module.contents = this.module!.contents;
module.contents = this.module.contents;
}
this.module = module;
}
this.dataRetrieved.emit(this.folderInstance || this.module);
this.description = this.folderInstance ? this.folderInstance.intro : this.module!.description;
this.subfolder = AddonModFolderHelper.formatContents(this.module!.contents);
this.description = this.folderInstance ? this.folderInstance.intro : this.module.description;
this.subfolder = AddonModFolderHelper.formatContents(this.module.contents);
} finally {
this.fillContextMenu(refresh);
}

View File

@ -135,7 +135,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
this.eventReceived.bind(this, false),
);
this.changeDiscObserver = CoreEvents.on(AddonModForumProvider.CHANGE_DISCUSSION_EVENT, data => {
if ((this.forum && this.forum.id === data.forumId) || data.cmId === this.module!.id) {
if ((this.forum && this.forum.id === data.forumId) || data.cmId === this.module.id) {
AddonModForum.invalidateDiscussionsList(this.forum!.id).finally(() => {
if (data.discussionId) {
// Discussion changed, search it in the list of discussions.
@ -198,7 +198,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
AddonModForum.instance
.logView(this.forum.id, this.forum.name)
.then(async () => {
CoreCourse.checkModuleCompletion(this.courseId!, this.module!.completiondata);
CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata);
return;
}),
@ -324,7 +324,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
promises.push(
AddonModForum.instance
.getAccessInformation(this.forum.id, { cmId: this.module!.id })
.getAccessInformation(this.forum.id, { cmId: this.module.id })
.then(async accessInfo => {
// Disallow adding discussions if cut-off date is reached and the user has not the
// capability to override it.
@ -341,7 +341,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
// Use the canAddDiscussion WS to check if the user can pin discussions.
promises.push(
AddonModForum.instance
.canAddDiscussionToAll(this.forum.id, { cmId: this.module!.id })
.canAddDiscussionToAll(this.forum.id, { cmId: this.module.id })
.then(async response => {
this.canPin = !!response.canpindiscussions;
@ -525,7 +525,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
protected async invalidateContent(): Promise<void> {
const promises: Promise<void>[] = [];
promises.push(AddonModForum.invalidateForumData(this.courseId!));
promises.push(AddonModForum.invalidateForumData(this.courseId));
if (this.forum) {
promises.push(AddonModForum.invalidateDiscussionsList(this.forum.id));
@ -546,7 +546,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
* @return Promise resolved when done.
*/
protected sync(): Promise<AddonModForumSyncResult> {
return AddonModForumPrefetchHandler.sync(this.module!, this.courseId!);
return AddonModForumPrefetchHandler.sync(this.module, this.courseId);
}
/**
@ -582,7 +582,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
isNewDiscussion: boolean,
data: AddonModForumNewDiscussionData | AddonModForumReplyDiscussionData,
): void {
if ((this.forum && this.forum.id === data.forumId) || data.cmId === this.module?.id) {
if ((this.forum && this.forum.id === data.forumId) || data.cmId === this.module.id) {
this.showLoadingAndRefresh(false).finally(() => {
// If it's a new discussion in tablet mode, try to open it.
if (isNewDiscussion && CoreScreen.isTablet) {
@ -606,7 +606,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
});
// Check completion since it could be configured to complete once the user adds a new discussion or replies.
CoreCourse.checkModuleCompletion(this.courseId!, this.module!.completiondata);
CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata);
}
}
@ -668,7 +668,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
componentProps: {
discussion,
forumId: this.forum!.id,
cmId: this.module!.id,
cmId: this.module.id,
},
event,
});
@ -733,7 +733,7 @@ class AddonModForumDiscussionsManager extends CorePageItemsListManager<Discussio
getItemQueryParams(discussion: DiscussionItem): Params {
return {
courseId: this.component.courseId,
cmId: this.component.module!.id,
cmId: this.component.module.id,
forumId: this.component.forum!.id,
...(this.isOnlineDiscussion(discussion) ? { discussion, trackPosts: this.component.trackPosts } : {}),
};

View File

@ -36,7 +36,7 @@
<div class="addon-mod-imscp-container">
<core-navigation-bar [previous]="previousItem" [next]="nextItem" (action)="loadItem($event)" [info]="description"
[title]="'core.description' | translate" [component]="component" [componentId]="componentId" contextLevel="module"
[contextInstanceId]="module?.id" [courseId]="courseId">
[contextInstanceId]="module.id" [courseId]="courseId">
</core-navigation-bar>
<core-iframe [src]="src"></core-iframe>
</div>

View File

@ -59,8 +59,8 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom
await this.loadContent();
try {
await AddonModImscp.logView(this.module!.instance!, this.module!.name);
CoreCourse.checkModuleCompletion(this.courseId!, this.module!.completiondata);
await AddonModImscp.logView(this.module.instance!, this.module.name);
CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata);
} catch {
// Ignore errors.
}
@ -72,7 +72,7 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom
* @return Resolved when done.
*/
protected async invalidateContent(): Promise<void> {
await AddonModImscp.invalidateContent(this.module!.id, this.courseId!);
await AddonModImscp.invalidateContent(this.module.id, this.courseId);
}
/**
@ -85,7 +85,7 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom
let downloadResult: CoreCourseResourceDownloadResult;
const promises: Promise<void>[] = [];
promises.push(AddonModImscp.getImscp(this.courseId!, this.module!.id).then((imscp) => {
promises.push(AddonModImscp.getImscp(this.courseId, this.module.id).then((imscp) => {
this.description = imscp.intro;
this.dataRetrieved.emit(imscp);
@ -101,7 +101,7 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom
try {
await Promise.all(promises);
this.items = AddonModImscp.createItemList(this.module!.contents);
this.items = AddonModImscp.createItemList(this.module.contents);
if (this.items.length && typeof this.currentItem == 'undefined') {
this.currentItem = this.items[0].href;
@ -129,7 +129,7 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom
* @return Promise resolved when done.
*/
async loadItem(itemId?: string): Promise<void> {
const src = await AddonModImscp.getIframeSrc(this.module!, itemId);
const src = await AddonModImscp.getIframeSrc(this.module, itemId);
this.currentItem = itemId;
this.previousItem = itemId ? AddonModImscp.getPreviousItem(this.items, itemId) : '';
this.nextItem = itemId ? AddonModImscp.getNextItem(this.items, itemId) : '';

View File

@ -32,7 +32,7 @@
<core-tab [title]="'addon.mod_lesson.preview' | translate" (ionSelect)="indexSelected()">
<ng-template>
<core-course-module-description [description]="description" [component]="component" [componentId]="componentId"
contextLevel="module" [contextInstanceId]="module?.id" [courseId]="courseId">
contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
</core-course-module-description>
<!-- Prevent access messages. Only show the first one. -->

View File

@ -146,7 +146,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
let lessonReady = true;
this.askPassword = false;
this.lesson = await AddonModLesson.getLesson(this.courseId!, this.module!.id);
this.lesson = await AddonModLesson.getLesson(this.courseId, this.module.id);
this.dataRetrieved.emit(this.lesson);
this.description = this.lesson.intro; // Show description only if intro is present.
@ -156,7 +156,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
await this.syncActivity(showErrors);
}
this.accessInfo = await AddonModLesson.getAccessInformation(this.lesson.id, { cmId: this.module!.id });
this.accessInfo = await AddonModLesson.getAccessInformation(this.lesson.id, { cmId: this.module.id });
this.canManage = this.accessInfo.canmanage;
this.canViewReports = this.accessInfo.canviewreports;
this.preventReasons = [];
@ -227,7 +227,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
}
const promises: Promise<unknown>[] = [];
const options = { cmId: this.module!.id };
const options = { cmId: this.module.id };
// Check if there is offline data.
promises.push(AddonModLessonSync.hasDataToSync(this.lesson.id, this.accessInfo.attemptscount).then((hasData) => {
@ -293,7 +293,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
protected hasSyncSucceed(result: AddonModLessonSyncResult): boolean {
if (result.updated || this.dataSent) {
// Check completion status if something was sent.
CoreCourse.checkModuleCompletion(this.courseId!, this.module!.completiondata);
CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata);
}
this.dataSent = false;
@ -339,7 +339,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
protected async invalidateContent(): Promise<void> {
const promises: Promise<unknown>[] = [];
promises.push(AddonModLesson.invalidateLessonData(this.courseId!));
promises.push(AddonModLesson.invalidateLessonData(this.courseId));
if (this.lesson) {
promises.push(AddonModLesson.invalidateAccessInformation(this.lesson.id));
@ -394,7 +394,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
AddonModLesson.logViewLesson(this.lesson.id, this.password, this.lesson.name),
);
CoreCourse.checkModuleCompletion(this.courseId!, this.module!.completiondata);
CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata);
}
/**
@ -414,7 +414,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
if (this.hasOffline) {
if (continueLast) {
pageId = await AddonModLesson.getLastPageSeen(this.lesson.id, this.accessInfo.attemptscount, {
cmId: this.module!.id,
cmId: this.module.id,
});
} else {
pageId = this.accessInfo.firstpageid;
@ -589,7 +589,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
this.showSpinner = true;
try {
await AddonModLessonPrefetchHandler.prefetch(this.module!, this.courseId, true);
await AddonModLessonPrefetchHandler.prefetch(this.module, this.courseId, true);
// Success downloading, open lesson.
this.playLesson(continueLast);
@ -661,8 +661,8 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
// The user sent data to server, but not in the sync process. Check if we need to fetch data.
await CoreUtils.ignoreErrors(AddonModLessonSync.prefetchAfterUpdate(
AddonModLessonPrefetchHandler.instance,
this.module!,
this.courseId!,
this.module,
this.courseId,
));
}
@ -677,7 +677,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
*/
protected async validatePassword(password: string): Promise<void> {
try {
this.lesson = await AddonModLesson.getLessonWithPassword(this.lesson!.id, { password, cmId: this.module!.id });
this.lesson = await AddonModLesson.getLessonWithPassword(this.lesson!.id, { password, cmId: this.module.id });
this.password = password;
} catch (error) {

View File

@ -13,7 +13,7 @@
</ion-toolbar>
</ion-header>
<ion-content>
<ion-refresher slot="fixed" [disabled]="!activityComponent?.loaded" (ionRefresh)="activityComponent?.doRefresh($event)">
<ion-refresher slot="fixed" [disabled]="!activityComponent?.loaded" (ionRefresh)="activityComponent?.doRefresh($event.target)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>

View File

@ -26,7 +26,7 @@
<core-loading [hideUntil]="loaded" class="core-loading-center safe-area-page">
<core-course-module-description *ngIf="displayDescription" [description]="description" [component]="component"
[componentId]="componentId" contextLevel="module" [contextInstanceId]="module?.id" [courseId]="courseId">
[componentId]="componentId" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
</core-course-module-description>
<ion-card class="core-warning-card" *ngIf="warning">
@ -36,7 +36,7 @@
<div class="ion-padding">
<core-format-text [component]="component" [componentId]="componentId" [text]="contents" contextLevel="module"
[contextInstanceId]="module?.id" [courseId]="courseId">
[contextInstanceId]="module.id" [courseId]="courseId">
</core-format-text>
<p class="ion-padding-bottom addon-mod_page-timemodified" *ngIf="displayTimemodified && timemodified">

View File

@ -59,8 +59,8 @@ export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComp
await this.loadContent();
try {
await AddonModPage.logView(this.module!.instance!, this.module!.name);
CoreCourse.checkModuleCompletion(this.courseId!, this.module!.completiondata);
await AddonModPage.logView(this.module.instance!, this.module.name);
CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata);
} catch {
// Ignore errors.
}
@ -72,7 +72,7 @@ export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComp
* @return Resolved when done.
*/
protected async invalidateContent(): Promise<void> {
await AddonModPage.invalidateContent(this.module!.id, this.courseId!);
await AddonModPage.invalidateContent(this.module.id, this.courseId);
}
/**
@ -92,9 +92,9 @@ export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComp
// Get the module to get the latest title and description. Data should've been updated in download.
if (this.canGetPage) {
getPagePromise = AddonModPage.getPageData(this.courseId!, this.module!.id);
getPagePromise = AddonModPage.getPageData(this.courseId, this.module.id);
} else {
getPagePromise = CoreCourse.getModule(this.module!.id, this.courseId!);
getPagePromise = CoreCourse.getModule(this.module.id, this.courseId);
}
promises.push(getPagePromise.then((page) => {
@ -133,7 +133,7 @@ export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComp
}));
// Get the page HTML.
promises.push(AddonModPageHelper.getPageHtml(this.module!.contents, this.module!.id).then((content) => {
promises.push(AddonModPageHelper.getPageHtml(this.module.contents, this.module.id).then((content) => {
this.contents = content;
this.warning = downloadResult?.failed ? this.getErrorDownloadingSomeFilesMessage(downloadResult.error!) : '';

View File

@ -28,7 +28,7 @@
<!-- Content. -->
<core-loading [hideUntil]="loaded" class="core-loading-center">
<core-course-module-description [description]="description" [component]="component" [componentId]="componentId"
contextLevel="module" [contextInstanceId]="module?.id" [courseId]="courseId">
contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
</core-course-module-description>
<!-- Access rules description messages. -->
@ -117,7 +117,7 @@
<ion-label>
<h3 class="item-heading">{{ 'addon.mod_quiz.comment' | translate }}</h3>
<p><core-format-text [component]="component" [componentId]="componentId" [text]="gradebookFeedback"
contextLevel="module" [contextInstanceId]="module?.id" [courseId]="courseId">
contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
</core-format-text></p>
</ion-label>
</ion-item>
@ -125,7 +125,7 @@
<ion-label>
<h3 class="item-heading">{{ 'addon.mod_quiz.overallfeedback' | translate }}</h3>
<p><core-format-text [component]="component" [componentId]="componentId" [text]="overallFeedback"
contextLevel="module" [contextInstanceId]="module?.id" [courseId]="courseId">
contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
</core-format-text></p>
</ion-label>
</ion-item>

View File

@ -129,7 +129,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
try {
await AddonModQuiz.logViewQuiz(this.quiz.id, this.quiz.name);
CoreCourse.checkModuleCompletion(this.courseId!, this.module!.completiondata);
CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata);
} catch {
// Ignore errors.
}
@ -162,7 +162,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
this.showStatusSpinner = true;
try {
await AddonModQuizPrefetchHandler.prefetch(this.module!, this.courseId, true);
await AddonModQuizPrefetchHandler.prefetch(this.module, this.courseId, true);
// Success downloading, open quiz.
this.openQuiz();
@ -190,7 +190,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<void> {
try {
// First get the quiz instance.
const quiz = await AddonModQuiz.getQuiz(this.courseId!, this.module!.id);
const quiz = await AddonModQuiz.getQuiz(this.courseId, this.module.id);
this.gradeMethodReadable = AddonModQuiz.getQuizGradeMethod(quiz.grademethod);
this.now = Date.now();
@ -231,7 +231,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
}
// Get quiz access info.
this.quizAccessInfo = await AddonModQuiz.getQuizAccessInformation(quiz.id, { cmId: this.module!.id });
this.quizAccessInfo = await AddonModQuiz.getQuizAccessInformation(quiz.id, { cmId: this.module.id });
this.showReviewColumn = this.quizAccessInfo.canreviewmyattempts;
this.accessRules = this.quizAccessInfo.accessrules;
@ -242,7 +242,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
}
// Get question types in the quiz.
const types = await AddonModQuiz.getQuizRequiredQtypes(quiz.id, { cmId: this.module!.id });
const types = await AddonModQuiz.getQuizRequiredQtypes(quiz.id, { cmId: this.module.id });
this.unsupportedQuestions = AddonModQuiz.getUnsupportedQuestions(types);
this.hasSupportedQuestions = !!types.find((type) => type != 'random' && this.unsupportedQuestions.indexOf(type) == -1);
@ -265,10 +265,10 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
protected async getAttempts(quiz: AddonModQuizQuizData): Promise<void> {
// Get access information of last attempt (it also works if no attempts made).
this.attemptAccessInfo = await AddonModQuiz.getAttemptAccessInformation(quiz.id, 0, { cmId: this.module!.id });
this.attemptAccessInfo = await AddonModQuiz.getAttemptAccessInformation(quiz.id, 0, { cmId: this.module.id });
// Get attempts.
const attempts = await AddonModQuiz.getUserAttempts(quiz.id, { cmId: this.module!.id });
const attempts = await AddonModQuiz.getUserAttempts(quiz.id, { cmId: this.module.id });
this.attempts = await this.treatAttempts(quiz, attempts);
@ -386,7 +386,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
if (quiz.showFeedbackColumn) {
// Get the quiz overall feedback.
const response = await AddonModQuiz.getFeedbackForGrade(quiz.id, this.gradebookData.grade, {
cmId: this.module!.id,
cmId: this.module.id,
});
this.overallFeedback = response.feedbacktext;
@ -404,14 +404,14 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
}
// If we go to auto review it means an attempt was finished. Check completion status.
CoreCourse.checkModuleCompletion(this.courseId!, this.module!.completiondata);
CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata);
// Verify that user can see the review.
const attemptId = this.autoReview.attemptId;
if (this.quizAccessInfo?.canreviewmyattempts) {
try {
await AddonModQuiz.getAttemptReview(attemptId, { page: -1, cmId: this.module!.id });
await AddonModQuiz.getAttemptReview(attemptId, { page: -1, cmId: this.module.id });
await CoreNavigator.navigate(`review/${attemptId}`);
} catch {
@ -429,7 +429,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
protected hasSyncSucceed(result: AddonModQuizSyncResult): boolean {
if (result.attemptFinished) {
// An attempt was finished, check completion status.
CoreCourse.checkModuleCompletion(this.courseId!, this.module!.completiondata);
CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata);
}
// If the sync call isn't rejected it means the sync was successful.
@ -488,7 +488,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
protected async invalidateContent(): Promise<void> {
const promises: Promise<void>[] = [];
promises.push(AddonModQuiz.invalidateQuizData(this.courseId!));
promises.push(AddonModQuiz.invalidateQuizData(this.courseId));
if (this.quiz) {
promises.push(AddonModQuiz.invalidateUserAttemptsForUser(this.quiz.id));
@ -497,7 +497,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
promises.push(AddonModQuiz.invalidateAttemptAccessInformation(this.quiz.id));
promises.push(AddonModQuiz.invalidateCombinedReviewOptionsForUser(this.quiz.id));
promises.push(AddonModQuiz.invalidateUserBestGradeForUser(this.quiz.id));
promises.push(AddonModQuiz.invalidateGradeFromGradebook(this.courseId!));
promises.push(AddonModQuiz.invalidateGradeFromGradebook(this.courseId));
}
await Promise.all(promises);
@ -536,7 +536,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
CoreNavigator.navigate('player', {
params: {
moduleUrl: this.module?.url,
moduleUrl: this.module.url,
},
});
}
@ -594,7 +594,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
}
// Get combined review options.
promises.push(AddonModQuiz.getCombinedReviewOptions(quiz.id, { cmId: this.module!.id }).then((options) => {
promises.push(AddonModQuiz.getCombinedReviewOptions(quiz.id, { cmId: this.module.id }).then((options) => {
this.options = options;
return;
@ -633,11 +633,11 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
* @return Promise resolved when done.
*/
protected async getQuizGrade(quiz: AddonModQuizQuizData): Promise<void> {
this.bestGrade = await AddonModQuiz.getUserBestGrade(quiz.id, { cmId: this.module!.id });
this.bestGrade = await AddonModQuiz.getUserBestGrade(quiz.id, { cmId: this.module.id });
try {
// Get gradebook grade.
const data = await AddonModQuiz.getGradeFromGradebook(this.courseId!, this.module!.id);
const data = await AddonModQuiz.getGradeFromGradebook(this.courseId, this.module.id);
if (data) {
this.gradebookData = {

View File

@ -22,7 +22,7 @@
<core-course-module-description *ngIf="mode != 'iframe' && (mode != 'embedded' || displayDescription)"
[description]="description" [component]="component" [componentId]="componentId" contextLevel="module"
[contextInstanceId]="module!.id" [courseId]="courseId">
[contextInstanceId]="module.id" [courseId]="courseId">
</core-course-module-description>
<ion-card class="core-warning-card" *ngIf="warning">

View File

@ -64,8 +64,8 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource
await this.loadContent();
try {
await AddonModResource.logView(this.module!.instance!, this.module!.name);
CoreCourse.checkModuleCompletion(this.courseId!, this.module!.completiondata);
await AddonModResource.logView(this.module.instance!, this.module.name);
CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata);
} catch {
// Ignore errors.
}
@ -77,7 +77,7 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource
* @return Resolved when done.
*/
protected async invalidateContent(): Promise<void> {
return AddonModResource.invalidateContent(this.module!.id, this.courseId!);
return AddonModResource.invalidateContent(this.module.id, this.courseId);
}
/**
@ -88,9 +88,9 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource
*/
protected async fetchContent(refresh?: boolean): Promise<void> {
// Load module contents if needed. Passing refresh is needed to force reloading contents.
await CoreCourse.loadModuleContents(this.module!, this.courseId, undefined, false, refresh);
await CoreCourse.loadModuleContents(this.module, this.courseId, undefined, false, refresh);
if (!this.module!.contents || !this.module!.contents.length) {
if (!this.module.contents || !this.module.contents.length) {
throw new CoreError(Translate.instant('core.filenotfound'));
}
@ -99,11 +99,11 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource
// Get the resource instance to get the latest name/description and to know if it's embedded.
if (this.canGetResource) {
resource = await CoreUtils.ignoreErrors(AddonModResource.getResourceData(this.courseId!, this.module!.id));
resource = await CoreUtils.ignoreErrors(AddonModResource.getResourceData(this.courseId, this.module.id));
this.description = resource?.intro || '';
options = resource?.displayoptions ? CoreTextUtils.unserialize(resource.displayoptions) : {};
} else {
resource = await CoreUtils.ignoreErrors(CoreCourse.getModule(this.module!.id, this.courseId));
resource = await CoreUtils.ignoreErrors(CoreCourse.getModule(this.module.id, this.courseId));
this.description = resource?.description || '';
options = resource?.customdata ? CoreTextUtils.unserialize(CoreTextUtils.parseJSON(resource.customdata)) : {};
}
@ -114,9 +114,9 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource
this.dataRetrieved.emit(resource);
}
if (AddonModResourceHelper.isDisplayedInIframe(this.module!)) {
if (AddonModResourceHelper.isDisplayedInIframe(this.module)) {
const downloadResult = await this.downloadResourceIfNeeded(refresh, true);
const src = await AddonModResourceHelper.getIframeSrc(this.module!);
const src = await AddonModResourceHelper.getIframeSrc(this.module);
this.mode = 'iframe';
if (this.src && src.toString() == this.src.toString()) {
@ -137,11 +137,11 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource
return;
}
if (resource && 'display' in resource && AddonModResourceHelper.isDisplayedEmbedded(this.module!, resource.display)) {
if (resource && 'display' in resource && AddonModResourceHelper.isDisplayedEmbedded(this.module, resource.display)) {
this.mode = 'embedded';
this.warning = '';
this.contentText = await AddonModResourceHelper.getEmbeddedHtml(this.module!, this.courseId!);
this.contentText = await AddonModResourceHelper.getEmbeddedHtml(this.module, this.courseId);
this.mode = this.contentText.length > 0 ? 'embedded' : 'external';
} else {
this.mode = 'external';
@ -158,20 +158,20 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource
* @return Promise resolved when done.
*/
async open(): Promise<void> {
let downloadable = await CoreCourseModulePrefetchDelegate.isModuleDownloadable(this.module!, this.courseId!);
let downloadable = await CoreCourseModulePrefetchDelegate.isModuleDownloadable(this.module, this.courseId);
if (downloadable) {
// Check if the main file is downloadle.
// This isn't done in "isDownloadable" to prevent extra WS calls in the course page.
downloadable = await AddonModResourceHelper.isMainFileDownloadable(this.module!);
downloadable = await AddonModResourceHelper.isMainFileDownloadable(this.module);
if (downloadable) {
return AddonModResourceHelper.openModuleFile(this.module!, this.courseId!);
return AddonModResourceHelper.openModuleFile(this.module, this.courseId);
}
}
// The resource cannot be downloaded, open the activity in browser.
await CoreSites.getCurrentSite()?.openInBrowserWithAutoLoginIfSameSite(this.module!.url!);
await CoreSites.getCurrentSite()?.openInBrowserWithAutoLoginIfSameSite(this.module.url!);
}
}

View File

@ -16,7 +16,7 @@
<core-loading [hideUntil]="loaded" class="core-loading-center">
<core-course-module-description *ngIf="displayDescription" [description]="description" [component]="component"
[componentId]="componentId" contextLevel="module" [contextInstanceId]="module!.id" [courseId]="courseId">
[componentId]="componentId" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
</core-course-module-description>
<div *ngIf="shouldIframe || (shouldEmbed && isOther)" class="addon-mod_url-embedded-url">

View File

@ -75,7 +75,7 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo
* @return Resolved when done.
*/
protected async invalidateContent(): Promise<void> {
await AddonModUrl.invalidateContent(this.module!.id, this.courseId!);
await AddonModUrl.invalidateContent(this.module.id, this.courseId);
}
/**
@ -90,7 +90,7 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo
throw null;
}
// Fetch the module data.
const url = await AddonModUrl.getUrl(this.courseId!, this.module!.id);
const url = await AddonModUrl.getUrl(this.courseId, this.module.id);
this.name = url.name;
this.description = url.intro;
@ -102,17 +102,17 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo
}
// Try to load module contents, it's needed to get the URL with parameters.
await CoreCourse.loadModuleContents(this.module!, this.courseId, undefined, false, refresh, undefined, 'url');
await CoreCourse.loadModuleContents(this.module, this.courseId, undefined, false, refresh, undefined, 'url');
// Always use the URL from the module because it already includes the parameters.
this.url = this.module!.contents[0] && this.module!.contents[0].fileurl ? this.module!.contents[0].fileurl : undefined;
this.url = this.module.contents[0] && this.module.contents[0].fileurl ? this.module.contents[0].fileurl : undefined;
await this.calculateDisplayOptions(url);
} catch {
// Fallback in case is not prefetched or not available.
const mod =
await CoreCourse.getModule(this.module!.id, this.courseId, undefined, false, false, undefined, 'url');
await CoreCourse.getModule(this.module.id, this.courseId, undefined, false, false, undefined, 'url');
this.name = mod.name;
this.description = mod.description;
@ -167,8 +167,8 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo
*/
protected async logView(): Promise<void> {
try {
await AddonModUrl.logView(this.module!.instance!, this.module!.name);
CoreCourse.checkModuleCompletion(this.courseId!, this.module!.completiondata);
await AddonModUrl.logView(this.module.instance!, this.module.name);
CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata);
} catch {
// Ignore errors.
}

View File

@ -50,8 +50,8 @@ export type CoreCourseResourceDownloadResult = {
})
export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, CoreCourseModuleMainComponent {
@Input() module?: CoreCourseModule; // The module of the component.
@Input() courseId?: number; // Course ID the component belongs to.
@Input() module!: CoreCourseModule; // The module of the component.
@Input() courseId!: number; // Course ID the component belongs to.
@Output() dataRetrieved = new EventEmitter<unknown>(); // Called to notify changes the index page from the main component.
loaded = false; // If the component has been loaded.
@ -90,10 +90,10 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
*/
async ngOnInit(): Promise<void> {
this.siteId = CoreSites.getCurrentSiteId();
this.description = this.module?.description;
this.componentId = this.module?.id;
this.externalUrl = this.module?.url;
this.courseId = this.courseId || this.module?.course;
this.description = this.module.description;
this.componentId = this.module.id;
this.externalUrl = this.module.url;
this.courseId = this.courseId || this.module.course!;
this.blog = await AddonBlog.isPluginEnabled();
}
@ -107,6 +107,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
*/
async doRefresh(refresher?: CustomEvent<IonRefresher> | null, done?: () => void, showErrors: boolean = false): Promise<void> {
if (!this.loaded || !this.module) {
// Module can be undefined if course format changes from single activity to weekly/topics.
return;
}
@ -193,12 +194,8 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
* Fill the context menu options
*/
protected fillContextMenu(refresh: boolean = false): void {
if (!this.module) {
return;
}
// All data obtained, now fill the context menu.
CoreCourseHelper.fillContextMenu(this, this.module, this.courseId!, refresh, this.component);
CoreCourseHelper.fillContextMenu(this, this.module, this.courseId, refresh, this.component);
}
/**
@ -215,10 +212,10 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
expandDescription(): void {
CoreTextUtils.viewText(Translate.instant('core.description'), this.description!, {
component: this.component,
componentId: this.module?.id,
componentId: this.module.id,
filter: true,
contextLevel: 'module',
instanceId: this.module?.id,
instanceId: this.module.id,
courseId: this.courseId,
});
}
@ -227,7 +224,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
* Go to blog posts.
*/
async gotoBlog(): Promise<void> {
const params: Params = { cmId: this.module?.id };
const params: Params = { cmId: this.module.id };
CoreNavigator.navigateToSitePath(AddonBlogMainMenuHandlerService.PAGE_NAME, { params });
}
@ -238,11 +235,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
* @param done Function to call when done.
*/
prefetch(done?: () => void): void {
if (!this.module) {
return;
}
CoreCourseHelper.contextMenuPrefetch(this, this.module, this.courseId!, done);
CoreCourseHelper.contextMenuPrefetch(this, this.module, this.courseId, done);
}
/**
@ -251,17 +244,13 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
* @param done Function to call when done.
*/
removeFiles(done?: () => void): void {
if (!this.module) {
return;
}
if (this.prefetchStatus == CoreConstants.DOWNLOADING) {
CoreDomUtils.showAlertTranslated(undefined, 'core.course.cannotdeletewhiledownloading');
return;
}
CoreCourseHelper.confirmAndRemoveFiles(this.module, this.courseId!, done);
CoreCourseHelper.confirmAndRemoveFiles(this.module, this.courseId, done);
}
/**
@ -309,13 +298,13 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
* @return Promise resolved when done.
*/
protected async setStatusListener(): Promise<void> {
if (typeof this.statusObserver != 'undefined' || !this.module) {
if (typeof this.statusObserver != 'undefined') {
return;
}
// Listen for changes on this module status.
this.statusObserver = CoreEvents.on(CoreEvents.PACKAGE_STATUS_CHANGED, (data) => {
if (!this.module || data.componentId != this.module.id || data.component != this.component) {
if (data.componentId != this.module.id || data.component != this.component) {
return;
}
@ -327,7 +316,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
}, this.siteId);
// Also, get the current status.
const status = await CoreCourseModulePrefetchDelegate.getModuleStatus(this.module, this.courseId!);
const status = await CoreCourseModulePrefetchDelegate.getModuleStatus(this.module, this.courseId);
this.currentStatus = status;
this.showStatus(status);
@ -350,17 +339,13 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
failed: false,
};
if (!this.module) {
return result;
}
// Get module status to determine if it needs to be downloaded.
await this.setStatusListener();
if (this.currentStatus != CoreConstants.DOWNLOADED) {
// Download content. This function also loads module contents if needed.
try {
await CoreCourseModulePrefetchDelegate.downloadModule(this.module, this.courseId!);
await CoreCourseModulePrefetchDelegate.downloadModule(this.module, this.courseId);
// If we reach here it means the download process already loaded the contents, no need to do it again.
contentsAlreadyLoaded = true;