MOBILE-3819 core: Remove code specific for Moodle 3.1-3.4
This commit is contained in:
		
							parent
							
								
									d944cb1978
								
							
						
					
					
						commit
						68e57c0f17
					
				| @ -39,7 +39,7 @@ export class AddonBadgesProvider { | |||||||
|     async isPluginEnabled(siteId?: string): Promise<boolean> { |     async isPluginEnabled(siteId?: string): Promise<boolean> { | ||||||
|         const site = await CoreSites.getSite(siteId); |         const site = await CoreSites.getSite(siteId); | ||||||
| 
 | 
 | ||||||
|         return site.canUseAdvancedFeature('enablebadges') && site.wsAvailable('core_course_get_user_navigation_options'); |         return site.canUseAdvancedFeature('enablebadges'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -194,7 +194,7 @@ export type AddonBadgesUserBadge = { | |||||||
|         targetframework?: string; // Target framework.
 |         targetframework?: string; // Target framework.
 | ||||||
|         targetcode?: string; // Target code.
 |         targetcode?: string; // Target code.
 | ||||||
|     }[]; |     }[]; | ||||||
|     competencies?: { // @deprecated from 3.7. @since 3.6. In 3.7 it was renamed to alignment.
 |     competencies?: { // @deprecatedonmoodle from 3.7. @since 3.6. In 3.7 it was renamed to alignment.
 | ||||||
|         id?: number; // Alignment id.
 |         id?: number; // Alignment id.
 | ||||||
|         badgeid?: number; // Badge id.
 |         badgeid?: number; // Badge id.
 | ||||||
|         targetname?: string; // Target name.
 |         targetname?: string; // Target name.
 | ||||||
|  | |||||||
| @ -16,10 +16,10 @@ import { Injectable } from '@angular/core'; | |||||||
| import { CoreBlockHandlerData } from '@features/block/services/block-delegate'; | import { CoreBlockHandlerData } from '@features/block/services/block-delegate'; | ||||||
| import { CoreBlockOnlyTitleComponent } from '@features/block/components/only-title-block/only-title-block'; | import { CoreBlockOnlyTitleComponent } from '@features/block/components/only-title-block/only-title-block'; | ||||||
| import { CoreBlockBaseHandler } from '@features/block/classes/base-block-handler'; | import { CoreBlockBaseHandler } from '@features/block/classes/base-block-handler'; | ||||||
| import { AddonCalendar } from '@/addons/calendar/services/calendar'; |  | ||||||
| import { CoreCourseBlock } from '@features/course/services/course'; | import { CoreCourseBlock } from '@features/course/services/course'; | ||||||
| import { Params } from '@angular/router'; | import { Params } from '@angular/router'; | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
|  | import { AddonCalendarMainMenuHandlerService } from '@addons/calendar/services/handlers/mainmenu'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Block handler. |  * Block handler. | ||||||
| @ -45,7 +45,7 @@ export class AddonBlockCalendarMonthHandlerService extends CoreBlockBaseHandler | |||||||
|             title: 'addon.block_calendarmonth.pluginname', |             title: 'addon.block_calendarmonth.pluginname', | ||||||
|             class: 'addon-block-calendar-month', |             class: 'addon-block-calendar-month', | ||||||
|             component: CoreBlockOnlyTitleComponent, |             component: CoreBlockOnlyTitleComponent, | ||||||
|             link: AddonCalendar.getMainCalendarPagePath(), |             link: AddonCalendarMainMenuHandlerService.PAGE_NAME, | ||||||
|             linkParams: linkParams, |             linkParams: linkParams, | ||||||
|             navOptions: { |             navOptions: { | ||||||
|                 preferCurrentTab: false, |                 preferCurrentTab: false, | ||||||
|  | |||||||
| @ -16,10 +16,10 @@ import { Injectable } from '@angular/core'; | |||||||
| import { CoreBlockHandlerData } from '@features/block/services/block-delegate'; | import { CoreBlockHandlerData } from '@features/block/services/block-delegate'; | ||||||
| import { CoreBlockOnlyTitleComponent } from '@features/block/components/only-title-block/only-title-block'; | import { CoreBlockOnlyTitleComponent } from '@features/block/components/only-title-block/only-title-block'; | ||||||
| import { CoreBlockBaseHandler } from '@features/block/classes/base-block-handler'; | import { CoreBlockBaseHandler } from '@features/block/classes/base-block-handler'; | ||||||
| import { AddonCalendar } from '@/addons/calendar/services/calendar'; |  | ||||||
| import { CoreCourseBlock } from '@features/course/services/course'; | import { CoreCourseBlock } from '@features/course/services/course'; | ||||||
| import { Params } from '@angular/router'; | import { Params } from '@angular/router'; | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
|  | import { AddonCalendarMainMenuHandlerService } from '@addons/calendar/services/handlers/mainmenu'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Block handler. |  * Block handler. | ||||||
| @ -46,7 +46,7 @@ export class AddonBlockCalendarUpcomingHandlerService extends CoreBlockBaseHandl | |||||||
|             title: 'addon.block_calendarupcoming.pluginname', |             title: 'addon.block_calendarupcoming.pluginname', | ||||||
|             class: 'addon-block-calendar-upcoming', |             class: 'addon-block-calendar-upcoming', | ||||||
|             component: CoreBlockOnlyTitleComponent, |             component: CoreBlockOnlyTitleComponent, | ||||||
|             link: AddonCalendar.getMainCalendarPagePath(), |             link: AddonCalendarMainMenuHandlerService.PAGE_NAME, | ||||||
|             linkParams: linkParams, |             linkParams: linkParams, | ||||||
|             navOptions: { |             navOptions: { | ||||||
|                 preferCurrentTab: false, |                 preferCurrentTab: false, | ||||||
|  | |||||||
| @ -248,13 +248,8 @@ export class AddonBlockTimelineProvider { | |||||||
|     async isAvailable(siteId?: string): Promise<boolean> { |     async isAvailable(siteId?: string): Promise<boolean> { | ||||||
|         const site = await CoreSites.getSite(siteId); |         const site = await CoreSites.getSite(siteId); | ||||||
| 
 | 
 | ||||||
|         // First check if dashboard is disabled.
 |         // Check if dashboard is disabled.
 | ||||||
|         if (CoreCoursesDashboard.isDisabledInSite(site)) { |         return !CoreCoursesDashboard.isDisabledInSite(site); | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return site.wsAvailable('core_calendar_get_action_events_by_courses') && |  | ||||||
|             site.wsAvailable('core_calendar_get_action_events_by_timesort'); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -40,11 +40,12 @@ export class AddonBlogProvider { | |||||||
|      * |      * | ||||||
|      * @param siteId Site ID. If not defined, current site. |      * @param siteId Site ID. If not defined, current site. | ||||||
|      * @return Promise resolved with true if enabled, resolved with false or rejected otherwise. |      * @return Promise resolved with true if enabled, resolved with false or rejected otherwise. | ||||||
|  |      * @since 3.6 | ||||||
|      */ |      */ | ||||||
|     async isPluginEnabled(siteId?: string): Promise<boolean> { |     async isPluginEnabled(siteId?: string): Promise<boolean> { | ||||||
|         const site = await CoreSites.getSite(siteId); |         const site = await CoreSites.getSite(siteId); | ||||||
| 
 | 
 | ||||||
|         return site.wsAvailable('core_blog_get_entries') &&site.canUseAdvancedFeature('enableblogs'); |         return site.wsAvailable('core_blog_get_entries') && site.canUseAdvancedFeature('enableblogs'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -38,13 +38,6 @@ function buildRoutes(injector: Injector): Routes { | |||||||
|             }, |             }, | ||||||
|             loadChildren: () => import('@/addons/calendar/pages/index/index.module').then(m => m.AddonCalendarIndexPageModule), |             loadChildren: () => import('@/addons/calendar/pages/index/index.module').then(m => m.AddonCalendarIndexPageModule), | ||||||
|         }, |         }, | ||||||
|         { |  | ||||||
|             path: 'list', |  | ||||||
|             data: { |  | ||||||
|                 mainMenuTabRoot: AddonCalendarMainMenuHandlerService.PAGE_NAME, |  | ||||||
|             }, |  | ||||||
|             loadChildren: () => import('@/addons/calendar/pages/list/list.module').then(m => m.AddonCalendarListPageModule), |  | ||||||
|         }, |  | ||||||
|         { |         { | ||||||
|             path: 'settings', |             path: 'settings', | ||||||
|             loadChildren: () => |             loadChildren: () => | ||||||
|  | |||||||
| @ -28,7 +28,7 @@ | |||||||
|         <core-context-menu-item [hidden]="!canEdit || !event || !event.canedit || event.deleted" [priority]="300" |         <core-context-menu-item [hidden]="!canEdit || !event || !event.canedit || event.deleted" [priority]="300" | ||||||
|             [content]="'core.edit' | translate" (action)="openEdit()" iconAction="fas-edit"> |             [content]="'core.edit' | translate" (action)="openEdit()" iconAction="fas-edit"> | ||||||
|         </core-context-menu-item> |         </core-context-menu-item> | ||||||
|         <core-context-menu-item [hidden]="!canDelete || !event || !event.candelete || event.deleted" [priority]="200" |         <core-context-menu-item [hidden]="!event || !event.candelete || event.deleted" [priority]="200" | ||||||
|             [content]="'core.delete' | translate" (action)="deleteEvent()" |             [content]="'core.delete' | translate" (action)="deleteEvent()" | ||||||
|             iconAction="fas-trash"></core-context-menu-item> |             iconAction="fas-trash"></core-context-menu-item> | ||||||
|         <core-context-menu-item [hidden]="!event || !event.deleted" [priority]="200" [content]="'core.restore' | translate" |         <core-context-menu-item [hidden]="!event || !event.deleted" [priority]="200" [content]="'core.restore' | translate" | ||||||
|  | |||||||
| @ -17,16 +17,12 @@ import { IonRefresher } from '@ionic/angular'; | |||||||
| import { AlertOptions } from '@ionic/core'; | import { AlertOptions } from '@ionic/core'; | ||||||
| import { | import { | ||||||
|     AddonCalendar, |     AddonCalendar, | ||||||
|     AddonCalendarEvent, |  | ||||||
|     AddonCalendarEventBase, |  | ||||||
|     AddonCalendarEventToDisplay, |     AddonCalendarEventToDisplay, | ||||||
|     AddonCalendarGetEventsEvent, |  | ||||||
|     AddonCalendarProvider, |     AddonCalendarProvider, | ||||||
| } from '../../services/calendar'; | } from '../../services/calendar'; | ||||||
| import { AddonCalendarHelper } from '../../services/calendar-helper'; | import { AddonCalendarHelper } from '../../services/calendar-helper'; | ||||||
| import { AddonCalendarOffline } from '../../services/calendar-offline'; | import { AddonCalendarOffline } from '../../services/calendar-offline'; | ||||||
| import { AddonCalendarSync, AddonCalendarSyncEvents, AddonCalendarSyncProvider } from '../../services/calendar-sync'; | import { AddonCalendarSync, AddonCalendarSyncEvents, AddonCalendarSyncProvider } from '../../services/calendar-sync'; | ||||||
| import { CoreCourses } from '@features/courses/services/courses'; |  | ||||||
| import { CoreApp } from '@services/app'; | import { CoreApp } from '@services/app'; | ||||||
| import { CoreEventObserver, CoreEvents } from '@singletons/events'; | import { CoreEventObserver, CoreEvents } from '@singletons/events'; | ||||||
| import { CoreDomUtils } from '@services/utils/dom'; | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
| @ -82,7 +78,6 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy { | |||||||
|     defaultTime = 0; |     defaultTime = 0; | ||||||
|     reminders: AddonCalendarReminderDBRecord[] = []; |     reminders: AddonCalendarReminderDBRecord[] = []; | ||||||
|     canEdit = false; |     canEdit = false; | ||||||
|     canDelete = false; |  | ||||||
|     hasOffline = false; |     hasOffline = false; | ||||||
|     isOnline = false; |     isOnline = false; | ||||||
|     syncIcon = CoreConstants.ICON_LOADING; // Sync icon.
 |     syncIcon = CoreConstants.ICON_LOADING; // Sync icon.
 | ||||||
| @ -99,9 +94,8 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy { | |||||||
|         this.currentSiteId = CoreSites.getCurrentSiteId(); |         this.currentSiteId = CoreSites.getCurrentSiteId(); | ||||||
|         this.isSplitViewOn = this.svComponent?.outletActivated; |         this.isSplitViewOn = this.svComponent?.outletActivated; | ||||||
| 
 | 
 | ||||||
|         // Check if site supports editing and deleting. No need to check allowed types, event.canedit already does it.
 |         // Check if site supports editing. No need to check allowed types, event.canedit already does it.
 | ||||||
|         this.canEdit = AddonCalendar.canEditEventsInSite(); |         this.canEdit = AddonCalendar.canEditEventsInSite(); | ||||||
|         this.canDelete = AddonCalendar.canDeleteEventsInSite(); |  | ||||||
| 
 | 
 | ||||||
|         // Listen for event edited. If current event is edited, reload the data.
 |         // Listen for event edited. If current event is edited, reload the data.
 | ||||||
|         this.editEventObserver = CoreEvents.on(AddonCalendarProvider.EDIT_EVENT_EVENT, (data) => { |         this.editEventObserver = CoreEvents.on(AddonCalendarProvider.EDIT_EVENT_EVENT, (data) => { | ||||||
| @ -168,8 +162,6 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy { | |||||||
|      * @return Promise resolved when done. |      * @return Promise resolved when done. | ||||||
|      */ |      */ | ||||||
|     async fetchEvent(sync = false, showErrors = false): Promise<void> { |     async fetchEvent(sync = false, showErrors = false): Promise<void> { | ||||||
|         const currentSite = CoreSites.getCurrentSite(); |  | ||||||
|         const canGetById = AddonCalendar.isGetEventByIdAvailableInSite(); |  | ||||||
|         let deleted = false; |         let deleted = false; | ||||||
| 
 | 
 | ||||||
|         this.isOnline = CoreApp.isOnline(); |         this.isOnline = CoreApp.isOnline(); | ||||||
| @ -209,13 +201,8 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             let event: AddonCalendarEvent | AddonCalendarEventBase | AddonCalendarGetEventsEvent; |  | ||||||
|             // Get the event data.
 |             // Get the event data.
 | ||||||
|             if (canGetById) { |             const event = await AddonCalendar.getEventById(this.eventId); | ||||||
|                 event = await AddonCalendar.getEventById(this.eventId); |  | ||||||
|             } else { |  | ||||||
|                 event = await AddonCalendar.getEvent(this.eventId); |  | ||||||
|             } |  | ||||||
|             this.event = AddonCalendarHelper.formatEventData(event); |             this.event = AddonCalendarHelper.formatEventData(event); | ||||||
| 
 | 
 | ||||||
|             try { |             try { | ||||||
| @ -254,34 +241,18 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy { | |||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 // Get the module URL.
 |                 // Get the module URL.
 | ||||||
|                 if (canGetById) { |  | ||||||
|                 this.moduleUrl = this.event!.url || ''; |                 this.moduleUrl = this.event!.url || ''; | ||||||
|             } |             } | ||||||
|             } |  | ||||||
| 
 | 
 | ||||||
|             const promises: Promise<void>[] = []; |             const promises: Promise<void>[] = []; | ||||||
| 
 | 
 | ||||||
|             const courseId = this.event.courseid; |             const courseId = this.event.courseid; | ||||||
|             if (courseId != this.siteHomeId) { |             if (courseId != this.siteHomeId) { | ||||||
|                 // If the event belongs to a course, get the course name and the URL to view it.
 |                 // If the event belongs to a course, get the course name and the URL to view it.
 | ||||||
|                 if (canGetById && this.event.course) { |                 if (this.event.course) { | ||||||
|                     this.courseId = this.event.course.id; |                     this.courseId = this.event.course.id; | ||||||
|                     this.courseName = this.event.course.fullname; |                     this.courseName = this.event.course.fullname; | ||||||
|                     this.courseUrl = this.event.course.viewurl; |                     this.courseUrl = this.event.course.viewurl; | ||||||
|                 } else if (!canGetById && this.event.courseid ) { |  | ||||||
|                     // Retrieve the course.
 |  | ||||||
|                     promises.push(CoreCourses.getUserCourse(this.event.courseid, true).then((course) => { |  | ||||||
|                         this.courseId = course.id; |  | ||||||
|                         this.courseName = course.fullname; |  | ||||||
|                         this.courseUrl = currentSite ? CoreTextUtils.concatenatePaths( |  | ||||||
|                             currentSite.siteUrl, |  | ||||||
|                             '/course/view.php?id=' + this.courseId, |  | ||||||
|                         ) : ''; |  | ||||||
| 
 |  | ||||||
|                         return; |  | ||||||
|                     }).catch(() => { |  | ||||||
|                         // Error getting course, just don't show the course name.
 |  | ||||||
|                     })); |  | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
| @ -299,7 +270,7 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy { | |||||||
|                 })); |                 })); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             if (canGetById && this.event.iscategoryevent && this.event.category) { |             if (this.event.iscategoryevent && this.event.category) { | ||||||
|                 this.categoryPath = this.event.category.nestedname; |                 this.categoryPath = this.event.category.nestedname; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,96 +0,0 @@ | |||||||
| <ion-header> |  | ||||||
|     <ion-toolbar> |  | ||||||
|         <ion-buttons slot="start"> |  | ||||||
|             <ion-back-button [text]="'core.back' | translate"></ion-back-button> |  | ||||||
|         </ion-buttons> |  | ||||||
|         <h1>{{ 'addon.calendar.calendarevents' | translate }}</h1> |  | ||||||
|         <ion-buttons slot="end"> |  | ||||||
|             <ion-button fill="clear" (click)="openFilter($event)" [attr.aria-label]="'core.filter' | translate"> |  | ||||||
|                 <ion-icon slot="icon-only" name="fas-filter" aria-hidden="true"></ion-icon> |  | ||||||
|             </ion-button> |  | ||||||
|             <core-context-menu> |  | ||||||
|                 <core-context-menu-item [hidden]="!notificationsEnabled" [priority]="600" |  | ||||||
|                 [content]="'core.settings.settings' | translate" (action)="openSettings()" iconAction="fas-cogs"> |  | ||||||
|             </core-context-menu-item> |  | ||||||
|                 <core-context-menu-item [hidden]="!eventsLoaded || !hasOffline || !isOnline" [priority]="400" |  | ||||||
|                 [content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(undefined, $event, true)" |  | ||||||
|                 [iconAction]="syncIcon" [closeOnClick]="false"></core-context-menu-item> |  | ||||||
|             </core-context-menu> |  | ||||||
|         </ion-buttons> |  | ||||||
|     </ion-toolbar> |  | ||||||
| </ion-header> |  | ||||||
| <ion-content> |  | ||||||
|     <ion-refresher slot="fixed" [disabled]="!eventsLoaded" (ionRefresh)="doRefresh($event.target)"> |  | ||||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> |  | ||||||
|     </ion-refresher> |  | ||||||
|     <core-loading [hideUntil]="eventsLoaded"> |  | ||||||
|         <!-- There is data to be synchronized --> |  | ||||||
|         <ion-card class="core-warning-card" *ngIf="hasOffline"> |  | ||||||
|             <ion-item> |  | ||||||
|                 <ion-icon name="fas-exclamation-triangle" slot="start" aria-hidden="true"></ion-icon> |  | ||||||
|                 <ion-label>{{ 'core.hasdatatosync' | translate:{$a: 'addon.calendar.calendar' | translate} }}</ion-label> |  | ||||||
|             </ion-item> |  | ||||||
|         </ion-card> |  | ||||||
| 
 |  | ||||||
|         <core-empty-box *ngIf="!filteredEvents || !filteredEvents.length" icon="fas-calendar" |  | ||||||
|             [message]="'addon.calendar.noevents' | translate"> |  | ||||||
|         </core-empty-box> |  | ||||||
| 
 |  | ||||||
|         <ion-list *ngIf="filteredEvents && filteredEvents.length" class="ion-no-margin"> |  | ||||||
|             <ng-container *ngFor="let event of filteredEvents"> |  | ||||||
|                 <ion-item-divider *ngIf="event.showDate"> |  | ||||||
|                     <ion-label><p class="item-heading">{{ event.timestart * 1000 | coreFormatDate: "strftimedayshort" }}</p></ion-label> |  | ||||||
|                 </ion-item-divider> |  | ||||||
|                 <ion-item class="addon-calendar-event ion-text-wrap" [attr.aria-label]="event.name" (click)="gotoEvent(event.id)" |  | ||||||
|                     [attr.aria-current]="event.id == eventId ? 'page' : 'false'" |  | ||||||
|                     [ngClass]="['addon-calendar-eventtype-'+event.eventtype]" button detail="true"> |  | ||||||
|                     <img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" slot="start" class="core-module-icon" alt="" |  | ||||||
|                         role="presentation"> |  | ||||||
|                     <ion-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" slot="start" |  | ||||||
|                         aria-hidden="true"> |  | ||||||
|                     </ion-icon> |  | ||||||
|                     <ion-label> |  | ||||||
|                         <p class="item-heading"> |  | ||||||
|                             <!-- Add the icon title so accessibility tools read it. --> |  | ||||||
|                             <span class="sr-only"> |  | ||||||
|                                 {{ 'addon.calendar.type' + event.formattedType | translate }} |  | ||||||
|                                 <span class="sr-only" *ngIf="event.moduleIcon && event.iconTitle">{{ event.iconTitle }}</span> |  | ||||||
|                             </span> |  | ||||||
|                             <core-format-text [text]="event.name" [contextLevel]="event.contextLevel" |  | ||||||
|                                 [contextInstanceId]="event.contextInstanceId"> |  | ||||||
|                             </core-format-text> |  | ||||||
|                         </p> |  | ||||||
|                         <p> |  | ||||||
|                             {{ event.timestart * 1000 | coreFormatDate: "strftimetime" }} |  | ||||||
|                             <span *ngIf="event.timeduration && event.endsSameDay"> |  | ||||||
|                                 - {{ (event.timestart + event.timeduration) * 1000 | coreFormatDate: "strftimetime" }} |  | ||||||
|                             </span> |  | ||||||
|                             <span *ngIf="event.timeduration && !event.endsSameDay"> |  | ||||||
|                                 - {{ (event.timestart + event.timeduration) * 1000 | coreFormatDate: "strftimedatetimeshort" }} |  | ||||||
|                             </span> |  | ||||||
|                         </p> |  | ||||||
|                     </ion-label> |  | ||||||
|                     <ion-note *ngIf="event.offline && !event.deleted" slot="end"> |  | ||||||
|                         <ion-icon name="fas-clock" aria-hidden="true"></ion-icon> |  | ||||||
|                         <span class="ion-text-wrap">{{ 'core.notsent' | translate }}</span> |  | ||||||
|                     </ion-note> |  | ||||||
|                     <ion-note *ngIf="event.deleted" slot="end"> |  | ||||||
|                         <ion-icon name="fas-trash" aria-hidden="true"></ion-icon> |  | ||||||
|                         <span class="ion-text-wrap">{{ 'core.deletedoffline' | translate }}</span> |  | ||||||
|                     </ion-note> |  | ||||||
|                 </ion-item> |  | ||||||
|             </ng-container> |  | ||||||
|         </ion-list> |  | ||||||
| 
 |  | ||||||
|         <core-infinite-loading [enabled]="canLoadMore" (action)="loadMoreEvents($event)" [error]="loadMoreError"> |  | ||||||
|         </core-infinite-loading> |  | ||||||
|     </core-loading> |  | ||||||
| 
 |  | ||||||
|     <!-- Create a calendar event. --> |  | ||||||
|     <ion-fab slot="fixed" core-fab vertical="bottom" horizontal="end" *ngIf="canCreate"> |  | ||||||
|         <ion-fab-button (click)="openEdit()" [attr.aria-label]="'addon.calendar.newevent' | translate"> |  | ||||||
|             <ion-icon name="fas-plus" aria-hidden="true"></ion-icon> |  | ||||||
|             <span class="sr-only">{{ 'addon.calendar.newevent' | translate }}</span> |  | ||||||
|         </ion-fab-button> |  | ||||||
|     </ion-fab> |  | ||||||
| </ion-content> |  | ||||||
| @ -1,60 +0,0 @@ | |||||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 |  | ||||||
| //
 |  | ||||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 |  | ||||||
| // you may not use this file except in compliance with the License.
 |  | ||||||
| // You may obtain a copy of the License at
 |  | ||||||
| //
 |  | ||||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 |  | ||||||
| //
 |  | ||||||
| // Unless required by applicable law or agreed to in writing, software
 |  | ||||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 |  | ||||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 |  | ||||||
| // See the License for the specific language governing permissions and
 |  | ||||||
| // limitations under the License.
 |  | ||||||
| 
 |  | ||||||
| import { NgModule } from '@angular/core'; |  | ||||||
| import { RouterModule, Routes } from '@angular/router'; |  | ||||||
| import { AddonCalendarEventRoute, AddonCalendarEditRoute } from '@addons/calendar/calendar-lazy.module'; |  | ||||||
| import { conditionalRoutes } from '@/app/app-routing.module'; |  | ||||||
| import { CoreScreen } from '@services/screen'; |  | ||||||
| 
 |  | ||||||
| import { CoreSharedModule } from '@/core/shared.module'; |  | ||||||
| 
 |  | ||||||
| import { AddonCalendarListPage } from './list.page'; |  | ||||||
| 
 |  | ||||||
| const splitviewRoutes = [AddonCalendarEditRoute, AddonCalendarEventRoute]; |  | ||||||
| 
 |  | ||||||
| const mobileRoutes: Routes = [ |  | ||||||
|     { |  | ||||||
|         path: '', |  | ||||||
|         component: AddonCalendarListPage, |  | ||||||
|     }, |  | ||||||
|     ...splitviewRoutes, |  | ||||||
| ]; |  | ||||||
| 
 |  | ||||||
| const tabletRoutes: Routes = [ |  | ||||||
|     { |  | ||||||
|         path: '', |  | ||||||
|         component: AddonCalendarListPage, |  | ||||||
|         children: [ |  | ||||||
|             ...splitviewRoutes, |  | ||||||
|         ], |  | ||||||
|     }, |  | ||||||
| ]; |  | ||||||
| 
 |  | ||||||
| const routes: Routes = [ |  | ||||||
|     ...conditionalRoutes(mobileRoutes, () => CoreScreen.isMobile), |  | ||||||
|     ...conditionalRoutes(tabletRoutes, () => CoreScreen.isTablet), |  | ||||||
| ]; |  | ||||||
| 
 |  | ||||||
| @NgModule({ |  | ||||||
|     imports: [ |  | ||||||
|         RouterModule.forChild(routes), |  | ||||||
|         CoreSharedModule, |  | ||||||
|     ], |  | ||||||
|     declarations: [ |  | ||||||
|         AddonCalendarListPage, |  | ||||||
|     ], |  | ||||||
|     exports: [RouterModule], |  | ||||||
| }) |  | ||||||
| export class AddonCalendarListPageModule {} |  | ||||||
| @ -1,643 +0,0 @@ | |||||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 |  | ||||||
| //
 |  | ||||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 |  | ||||||
| // you may not use this file except in compliance with the License.
 |  | ||||||
| // You may obtain a copy of the License at
 |  | ||||||
| //
 |  | ||||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 |  | ||||||
| //
 |  | ||||||
| // Unless required by applicable law or agreed to in writing, software
 |  | ||||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 |  | ||||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 |  | ||||||
| // See the License for the specific language governing permissions and
 |  | ||||||
| // limitations under the License.
 |  | ||||||
| 
 |  | ||||||
| import { Component, ViewChild, OnDestroy, OnInit } from '@angular/core'; |  | ||||||
| import { IonContent, IonRefresher } from '@ionic/angular'; |  | ||||||
| import { |  | ||||||
|     AddonCalendarProvider, |  | ||||||
|     AddonCalendar, |  | ||||||
|     AddonCalendarEventToDisplay, |  | ||||||
| } from '../../services/calendar'; |  | ||||||
| import { AddonCalendarOffline } from '../../services/calendar-offline'; |  | ||||||
| import { AddonCalendarFilter, AddonCalendarHelper } from '../../services/calendar-helper'; |  | ||||||
| import { AddonCalendarSync, AddonCalendarSyncProvider } from '../../services/calendar-sync'; |  | ||||||
| import { CoreCategoryData, CoreCourses, CoreEnrolledCourseData } from '@features/courses/services/courses'; |  | ||||||
| import { CoreDomUtils } from '@services/utils/dom'; |  | ||||||
| import { CoreTimeUtils } from '@services/utils/time'; |  | ||||||
| import { CoreSites } from '@services/sites'; |  | ||||||
| import { CoreLocalNotifications } from '@services/local-notifications'; |  | ||||||
| import { CoreEventObserver, CoreEvents } from '@singletons/events'; |  | ||||||
| import { CoreApp } from '@services/app'; |  | ||||||
| import moment from 'moment'; |  | ||||||
| import { CoreConstants } from '@/core/constants'; |  | ||||||
| import { AddonCalendarFilterPopoverComponent } from '../../components/filter/filter'; |  | ||||||
| import { Params } from '@angular/router'; |  | ||||||
| import { Subscription } from 'rxjs'; |  | ||||||
| import { Network, NgZone } from '@singletons'; |  | ||||||
| import { CoreCoursesHelper } from '@features/courses/services/courses-helper'; |  | ||||||
| import { CoreUtils } from '@services/utils/utils'; |  | ||||||
| import { CoreNavigator } from '@services/navigator'; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Page that displays the list of calendar events. |  | ||||||
|  */ |  | ||||||
| @Component({ |  | ||||||
|     selector: 'page-addon-calendar-list', |  | ||||||
|     templateUrl: 'list.html', |  | ||||||
|     styleUrls: ['../../calendar-common.scss', 'list.scss'], |  | ||||||
| }) |  | ||||||
| export class AddonCalendarListPage implements OnInit, OnDestroy { |  | ||||||
| 
 |  | ||||||
|     @ViewChild(IonContent) content?: IonContent; |  | ||||||
| 
 |  | ||||||
|     protected initialTime = 0; |  | ||||||
|     protected daysLoaded = 0; |  | ||||||
|     protected emptyEventsTimes = 0; // Variable to identify consecutive calls returning 0 events.
 |  | ||||||
|     protected categoriesRetrieved = false; |  | ||||||
|     protected getCategories = false; |  | ||||||
|     protected categories: { [id: number]: CoreCategoryData } = {}; |  | ||||||
|     protected siteHomeId: number; |  | ||||||
|     protected currentSiteId: string; |  | ||||||
|     protected onlineEvents: AddonCalendarEventToDisplay[] = []; |  | ||||||
|     protected offlineEvents: AddonCalendarEventToDisplay[] = []; |  | ||||||
|     protected deletedEvents: number [] = []; |  | ||||||
| 
 |  | ||||||
|     // Observers.
 |  | ||||||
|     protected obsDefaultTimeChange?: CoreEventObserver; |  | ||||||
|     protected newEventObserver: CoreEventObserver; |  | ||||||
|     protected discardedObserver: CoreEventObserver; |  | ||||||
|     protected editEventObserver: CoreEventObserver; |  | ||||||
|     protected deleteEventObserver: CoreEventObserver; |  | ||||||
|     protected undeleteEventObserver: CoreEventObserver; |  | ||||||
|     protected syncObserver: CoreEventObserver; |  | ||||||
|     protected manualSyncObserver: CoreEventObserver; |  | ||||||
|     protected filterChangedObserver: CoreEventObserver; |  | ||||||
|     protected onlineObserver: Subscription; |  | ||||||
| 
 |  | ||||||
|     eventId?: number; // Selected EventId on list
 |  | ||||||
|     courses: Partial<CoreEnrolledCourseData>[] = []; |  | ||||||
|     eventsLoaded = false; |  | ||||||
|     events: AddonCalendarEventToDisplay[] = []; // Events (both online and offline).
 |  | ||||||
|     notificationsEnabled = false; |  | ||||||
|     filteredEvents: AddonCalendarEventToDisplay[] = []; |  | ||||||
|     canLoadMore = false; |  | ||||||
|     loadMoreError = false; |  | ||||||
|     canCreate = false; |  | ||||||
|     hasOffline = false; |  | ||||||
|     isOnline = false; |  | ||||||
|     syncIcon = CoreConstants.ICON_LOADING; |  | ||||||
|     filter: AddonCalendarFilter = { |  | ||||||
|         filtered: false, |  | ||||||
|         courseId: undefined, |  | ||||||
|         categoryId: undefined, |  | ||||||
|         course: true, |  | ||||||
|         group: true, |  | ||||||
|         site: true, |  | ||||||
|         user: true, |  | ||||||
|         category: true, |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     constructor() { |  | ||||||
| 
 |  | ||||||
|         this.siteHomeId = CoreSites.getCurrentSiteHomeId(); |  | ||||||
|         this.notificationsEnabled = CoreLocalNotifications.isAvailable(); |  | ||||||
|         this.currentSiteId = CoreSites.getCurrentSiteId(); |  | ||||||
| 
 |  | ||||||
|         if (this.notificationsEnabled) { |  | ||||||
|             // Re-schedule events if default time changes.
 |  | ||||||
|             this.obsDefaultTimeChange = CoreEvents.on(AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_CHANGED, () => { |  | ||||||
|                 AddonCalendar.scheduleEventsNotifications(this.onlineEvents); |  | ||||||
|             }, this.currentSiteId); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // Listen for events added. When an event is added, reload the data.
 |  | ||||||
|         this.newEventObserver = CoreEvents.on(AddonCalendarProvider.NEW_EVENT_EVENT, (data) => { |  | ||||||
|             if (data && data.eventId) { |  | ||||||
|                 this.eventsLoaded = false; |  | ||||||
|                 this.refreshEvents(true, false); |  | ||||||
|             } |  | ||||||
|         }, this.currentSiteId); |  | ||||||
| 
 |  | ||||||
|         // Listen for new event discarded event. When it does, reload the data.
 |  | ||||||
|         this.discardedObserver = CoreEvents.on(AddonCalendarProvider.NEW_EVENT_DISCARDED_EVENT, () => { |  | ||||||
|             this.eventsLoaded = false; |  | ||||||
|             this.refreshEvents(true, false); |  | ||||||
|         }, this.currentSiteId); |  | ||||||
| 
 |  | ||||||
|         // Listen for events edited. When an event is edited, reload the data.
 |  | ||||||
|         this.editEventObserver = CoreEvents.on(AddonCalendarProvider.EDIT_EVENT_EVENT, (data) => { |  | ||||||
|             if (data && data.eventId) { |  | ||||||
|                 this.eventsLoaded = false; |  | ||||||
|                 this.refreshEvents(true, false); |  | ||||||
|             } |  | ||||||
|         }, this.currentSiteId); |  | ||||||
| 
 |  | ||||||
|         // Refresh data if calendar events are synchronized automatically.
 |  | ||||||
|         this.syncObserver = CoreEvents.on(AddonCalendarSyncProvider.AUTO_SYNCED, () => { |  | ||||||
|             this.eventsLoaded = false; |  | ||||||
|             this.refreshEvents(); |  | ||||||
|         }, this.currentSiteId); |  | ||||||
| 
 |  | ||||||
|         // Refresh data if calendar events are synchronized manually but not by this page.
 |  | ||||||
|         this.manualSyncObserver = CoreEvents.on(AddonCalendarSyncProvider.MANUAL_SYNCED, (data) => { |  | ||||||
|             if (data && data.source != 'list') { |  | ||||||
|                 this.eventsLoaded = false; |  | ||||||
|                 this.refreshEvents(); |  | ||||||
|             } |  | ||||||
|         }, this.currentSiteId); |  | ||||||
| 
 |  | ||||||
|         // Update the list when an event is deleted.
 |  | ||||||
|         this.deleteEventObserver = CoreEvents.on( |  | ||||||
|             AddonCalendarProvider.DELETED_EVENT_EVENT, |  | ||||||
|             (data) => { |  | ||||||
|                 if (data && !data.sent) { |  | ||||||
|                     // Event was deleted in offline. Just mark it as deleted, no need to refresh.
 |  | ||||||
|                     this.markAsDeleted(data.eventId, true); |  | ||||||
|                     this.deletedEvents.push(data.eventId); |  | ||||||
|                     this.hasOffline = true; |  | ||||||
|                 } else { |  | ||||||
|                     // Event deleted, refresh the view.
 |  | ||||||
|                     this.eventsLoaded = false; |  | ||||||
|                     this.refreshEvents(); |  | ||||||
|                 } |  | ||||||
|             }, |  | ||||||
|             this.currentSiteId, |  | ||||||
|         ); |  | ||||||
| 
 |  | ||||||
|         // Listen for events "undeleted" (offline).
 |  | ||||||
|         this.undeleteEventObserver = CoreEvents.on( |  | ||||||
|             AddonCalendarProvider.UNDELETED_EVENT_EVENT, |  | ||||||
|             (data) => { |  | ||||||
|                 if (!data || !data.eventId) { |  | ||||||
|                     return; |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 // Mark it as undeleted, no need to refresh.
 |  | ||||||
|                 this.markAsDeleted(data.eventId, false); |  | ||||||
| 
 |  | ||||||
|                 // Remove it from the list of deleted events if it's there.
 |  | ||||||
|                 const index = this.deletedEvents.indexOf(data.eventId); |  | ||||||
|                 if (index != -1) { |  | ||||||
|                     this.deletedEvents.splice(index, 1); |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 this.hasOffline = !!this.offlineEvents.length || !!this.deletedEvents.length; |  | ||||||
|             }, |  | ||||||
|             this.currentSiteId, |  | ||||||
|         ); |  | ||||||
| 
 |  | ||||||
|         this.filterChangedObserver = |  | ||||||
|             CoreEvents.on(AddonCalendarProvider.FILTER_CHANGED_EVENT, async (data) => { |  | ||||||
|                 this.filter = data; |  | ||||||
| 
 |  | ||||||
|                 // Course viewed has changed, check if the user can create events for this course calendar.
 |  | ||||||
|                 this.canCreate = await AddonCalendarHelper.canEditEvents(this.filter.courseId); |  | ||||||
| 
 |  | ||||||
|                 this.filterEvents(); |  | ||||||
| 
 |  | ||||||
|                 this.content?.scrollToTop(); |  | ||||||
|             }); |  | ||||||
| 
 |  | ||||||
|         // Refresh online status when changes.
 |  | ||||||
|         this.onlineObserver = Network.onChange().subscribe(() => { |  | ||||||
|             // Execute the callback in the Angular zone, so change detection doesn't stop working.
 |  | ||||||
|             NgZone.run(() => { |  | ||||||
|                 this.isOnline = CoreApp.isOnline(); |  | ||||||
|             }); |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * View loaded. |  | ||||||
|      */ |  | ||||||
|     async ngOnInit(): Promise<void> { |  | ||||||
|         this.filter.courseId = CoreNavigator.getRouteNumberParam('courseId'); |  | ||||||
|         this.syncIcon = CoreConstants.ICON_LOADING; |  | ||||||
| 
 |  | ||||||
|         await this.fetchData(false, true, false); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Fetch all the data required for the view. |  | ||||||
|      * |  | ||||||
|      * @param refresh Empty events array first. |  | ||||||
|      * @param sync Whether it should try to synchronize offline events. |  | ||||||
|      * @param showErrors Whether to show sync errors to the user. |  | ||||||
|      * @return Promise resolved when done. |  | ||||||
|      */ |  | ||||||
|     async fetchData(refresh = false, sync = false, showErrors = false): Promise<void> { |  | ||||||
|         this.initialTime = CoreTimeUtils.timestamp(); |  | ||||||
|         this.daysLoaded = 0; |  | ||||||
|         this.emptyEventsTimes = 0; |  | ||||||
|         this.isOnline = CoreApp.isOnline(); |  | ||||||
| 
 |  | ||||||
|         if (sync) { |  | ||||||
|             // Try to synchronize offline events.
 |  | ||||||
|             try { |  | ||||||
|                 const result = await AddonCalendarSync.syncEvents(); |  | ||||||
|                 if (result.warnings && result.warnings.length) { |  | ||||||
|                     CoreDomUtils.showErrorModal(result.warnings[0]); |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 if (result.updated) { |  | ||||||
|                     // Trigger a manual sync event.
 |  | ||||||
|                     result.source = 'list'; |  | ||||||
| 
 |  | ||||||
|                     CoreEvents.trigger( |  | ||||||
|                         AddonCalendarSyncProvider.MANUAL_SYNCED, |  | ||||||
|                         result, |  | ||||||
|                         this.currentSiteId, |  | ||||||
|                     ); |  | ||||||
|                 } |  | ||||||
|             } catch (error) { |  | ||||||
|                 if (showErrors) { |  | ||||||
|                     CoreDomUtils.showErrorModalDefault(error, 'core.errorsync', true); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         try { |  | ||||||
|             const promises: Promise<void>[] = []; |  | ||||||
| 
 |  | ||||||
|             this.hasOffline = false; |  | ||||||
| 
 |  | ||||||
|             promises.push(AddonCalendarHelper.canEditEvents(this.filter.courseId).then((canEdit) => { |  | ||||||
|                 this.canCreate = canEdit; |  | ||||||
| 
 |  | ||||||
|                 return; |  | ||||||
|             })); |  | ||||||
| 
 |  | ||||||
|             // Load courses for the popover.
 |  | ||||||
|             promises.push(CoreCoursesHelper.getCoursesForPopover(this.filter.courseId).then((result) => { |  | ||||||
|                 this.courses = result.courses; |  | ||||||
| 
 |  | ||||||
|                 return this.fetchEvents(refresh); |  | ||||||
|             })); |  | ||||||
| 
 |  | ||||||
|             // Get offline events.
 |  | ||||||
|             promises.push(AddonCalendarOffline.getAllEditedEvents().then((offlineEvents) => { |  | ||||||
|                 this.hasOffline = this.hasOffline || !!offlineEvents.length; |  | ||||||
| 
 |  | ||||||
|                 // Format data and sort by timestart.
 |  | ||||||
|                 const events: AddonCalendarEventToDisplay[] = offlineEvents.map((event) => |  | ||||||
|                     AddonCalendarHelper.formatOfflineEventData(event)); |  | ||||||
| 
 |  | ||||||
|                 this.offlineEvents = AddonCalendarHelper.sortEvents(events); |  | ||||||
| 
 |  | ||||||
|                 return; |  | ||||||
|             })); |  | ||||||
| 
 |  | ||||||
|             // Get events deleted in offline.
 |  | ||||||
|             promises.push(AddonCalendarOffline.getAllDeletedEventsIds().then((ids) => { |  | ||||||
|                 this.hasOffline = this.hasOffline || !!ids.length; |  | ||||||
|                 this.deletedEvents = ids; |  | ||||||
| 
 |  | ||||||
|                 return; |  | ||||||
|             })); |  | ||||||
| 
 |  | ||||||
|             await Promise.all(promises); |  | ||||||
|         } catch (error) { |  | ||||||
|             CoreDomUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         this.eventsLoaded = true; |  | ||||||
|         this.syncIcon = CoreConstants.ICON_SYNC; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Fetches the events and updates the view. |  | ||||||
|      * |  | ||||||
|      * @param refresh Empty events array first. |  | ||||||
|      * @return Promise resolved when done. |  | ||||||
|      */ |  | ||||||
|     async fetchEvents(refresh = false): Promise<void> { |  | ||||||
|         this.loadMoreError = false; |  | ||||||
| 
 |  | ||||||
|         try { |  | ||||||
|             const onlineEventsTemp = |  | ||||||
|                 await AddonCalendar.getEventsList(this.initialTime, this.daysLoaded, AddonCalendarProvider.DAYS_INTERVAL); |  | ||||||
| 
 |  | ||||||
|             if (onlineEventsTemp.length === 0) { |  | ||||||
|                 this.emptyEventsTimes++; |  | ||||||
|                 if (this.emptyEventsTimes > 5) { // Stop execution if we retrieve empty list 6 consecutive times.
 |  | ||||||
|                     this.canLoadMore = false; |  | ||||||
|                     if (refresh) { |  | ||||||
|                         this.onlineEvents = []; |  | ||||||
|                         this.filteredEvents = []; |  | ||||||
|                         this.events = this.offlineEvents; |  | ||||||
|                     } |  | ||||||
|                 } else { |  | ||||||
|                     // No events returned, load next events.
 |  | ||||||
|                     this.daysLoaded += AddonCalendarProvider.DAYS_INTERVAL; |  | ||||||
| 
 |  | ||||||
|                     return this.fetchEvents(); |  | ||||||
|                 } |  | ||||||
|             } else { |  | ||||||
|                 const onlineEvents = onlineEventsTemp.map((event) => AddonCalendarHelper.formatEventData(event)); |  | ||||||
| 
 |  | ||||||
|                 // Get the merged events of this period.
 |  | ||||||
|                 const events = this.mergeEvents(onlineEvents); |  | ||||||
| 
 |  | ||||||
|                 this.getCategories = this.shouldLoadCategories(onlineEvents); |  | ||||||
| 
 |  | ||||||
|                 if (refresh) { |  | ||||||
|                     this.onlineEvents = onlineEvents; |  | ||||||
|                     this.events = events; |  | ||||||
|                 } else { |  | ||||||
|                     // Filter events with same ID. Repeated events are returned once per WS call, show them only once.
 |  | ||||||
|                     this.onlineEvents = CoreUtils.mergeArraysWithoutDuplicates(this.onlineEvents, onlineEvents, 'id'); |  | ||||||
|                     this.events = CoreUtils.mergeArraysWithoutDuplicates(this.events, events, 'id'); |  | ||||||
|                 } |  | ||||||
|                 this.filterEvents(); |  | ||||||
| 
 |  | ||||||
|                 // Calculate which evemts need to display the date.
 |  | ||||||
|                 this.filteredEvents.forEach((event, index) => { |  | ||||||
|                     event.showDate = this.showDate(event, this.filteredEvents[index - 1]); |  | ||||||
|                     event.endsSameDay = this.endsSameDay(event); |  | ||||||
|                 }); |  | ||||||
|                 this.canLoadMore = true; |  | ||||||
| 
 |  | ||||||
|                 // Schedule notifications for the events retrieved (might have new events).
 |  | ||||||
|                 AddonCalendar.scheduleEventsNotifications(this.onlineEvents); |  | ||||||
| 
 |  | ||||||
|                 this.daysLoaded += AddonCalendarProvider.DAYS_INTERVAL; |  | ||||||
|             } |  | ||||||
|         } catch (error) { |  | ||||||
|             CoreDomUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true); |  | ||||||
|             this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading.
 |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // Success retrieving events. Get categories if needed.
 |  | ||||||
|         if (this.getCategories) { |  | ||||||
|             this.getCategories = false; |  | ||||||
| 
 |  | ||||||
|             return this.loadCategories(); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Function to load more events. |  | ||||||
|      * |  | ||||||
|      * @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading. |  | ||||||
|      * @return Resolved when done. |  | ||||||
|      */ |  | ||||||
|     loadMoreEvents(infiniteComplete?: () => void ): void { |  | ||||||
|         this.fetchEvents().finally(() => { |  | ||||||
|             infiniteComplete && infiniteComplete(); |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     protected filterEvents(): void { |  | ||||||
|         this.filteredEvents = AddonCalendarHelper.getFilteredEvents(this.events, this.filter, this.categories); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Returns if the current state should load categories or not. |  | ||||||
|      * |  | ||||||
|      * @param events Events to parse. |  | ||||||
|      * @return True if categories should be loaded. |  | ||||||
|      */ |  | ||||||
|     protected shouldLoadCategories(events: AddonCalendarEventToDisplay[]): boolean { |  | ||||||
|         if (this.categoriesRetrieved || this.getCategories) { |  | ||||||
|             // Use previous value
 |  | ||||||
|             return this.getCategories; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // Categories not loaded yet. We should get them if there's any category event.
 |  | ||||||
|         const found = events.some((event) => typeof event.categoryid != 'undefined' && event.categoryid > 0); |  | ||||||
| 
 |  | ||||||
|         return found || this.getCategories; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Load categories to be able to filter events. |  | ||||||
|      * |  | ||||||
|      * @return Promise resolved when done. |  | ||||||
|      */ |  | ||||||
|     protected async loadCategories(): Promise<void> { |  | ||||||
|         try { |  | ||||||
|             const cats = await CoreCourses.getCategories(0, true); |  | ||||||
|             this.categoriesRetrieved = true; |  | ||||||
|             this.categories = {}; |  | ||||||
|             // Index categories by ID.
 |  | ||||||
|             cats.forEach((category) => { |  | ||||||
|                 this.categories[category.id] = category; |  | ||||||
|             }); |  | ||||||
|         } catch { |  | ||||||
|             // Ignore errors.
 |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Merge a period of online events with the offline events of that period. |  | ||||||
|      * |  | ||||||
|      * @param onlineEvents Online events. |  | ||||||
|      * @return Merged events. |  | ||||||
|      */ |  | ||||||
|     protected mergeEvents(onlineEvents: AddonCalendarEventToDisplay[]): AddonCalendarEventToDisplay[] { |  | ||||||
|         if (!this.offlineEvents.length && !this.deletedEvents.length) { |  | ||||||
|             // No offline events, nothing to merge.
 |  | ||||||
|             return onlineEvents; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         const start = this.initialTime + (CoreConstants.SECONDS_DAY * this.daysLoaded); |  | ||||||
|         const end = start + (CoreConstants.SECONDS_DAY * AddonCalendarProvider.DAYS_INTERVAL) - 1; |  | ||||||
|         let result = onlineEvents; |  | ||||||
| 
 |  | ||||||
|         if (this.deletedEvents.length) { |  | ||||||
|             // Mark as deleted the events that were deleted in offline.
 |  | ||||||
|             result.forEach((event) => { |  | ||||||
|                 event.deleted = this.deletedEvents.indexOf(event.id) != -1; |  | ||||||
|             }); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if (this.offlineEvents.length) { |  | ||||||
|             // Remove the online events that were modified in offline.
 |  | ||||||
|             result = result.filter((event) => { |  | ||||||
|                 const offlineEvent = this.offlineEvents.find((ev) => ev.id == event.id); |  | ||||||
| 
 |  | ||||||
|                 return !offlineEvent; |  | ||||||
|             }); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // Now get the offline events that belong to this period.
 |  | ||||||
|         const periodOfflineEvents = this.offlineEvents.filter((event) => { |  | ||||||
|             if (this.daysLoaded == 0 && event.timestart < start) { |  | ||||||
|                 // Display offline events that are previous to current time to allow editing them.
 |  | ||||||
|                 return true; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             return (event.timestart >= start || event.timestart + event.timeduration >= start) && event.timestart <= end; |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         // Merge both arrays and sort them.
 |  | ||||||
|         result = result.concat(periodOfflineEvents); |  | ||||||
| 
 |  | ||||||
|         return AddonCalendarHelper.sortEvents(result); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Refresh the data. |  | ||||||
|      * |  | ||||||
|      * @param refresher Refresher. |  | ||||||
|      * @param done Function to call when done. |  | ||||||
|      * @param showErrors Whether to show sync errors to the user. |  | ||||||
|      * @return Promise resolved when done. |  | ||||||
|      */ |  | ||||||
|     async doRefresh(refresher?: IonRefresher, done?: () => void, showErrors?: boolean): Promise<void> { |  | ||||||
|         if (!this.eventsLoaded) { |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         await this.refreshEvents(true, showErrors).finally(() => { |  | ||||||
|             refresher?.complete(); |  | ||||||
|             done && done(); |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Refresh the events. |  | ||||||
|      * |  | ||||||
|      * @param sync Whether it should try to synchronize offline events. |  | ||||||
|      * @param showErrors Whether to show sync errors to the user. |  | ||||||
|      * @return Promise resolved when done. |  | ||||||
|      */ |  | ||||||
|     async refreshEvents(sync?: boolean, showErrors?: boolean): Promise<void> { |  | ||||||
|         this.syncIcon = CoreConstants.ICON_LOADING; |  | ||||||
| 
 |  | ||||||
|         const promises: Promise<void>[] = []; |  | ||||||
| 
 |  | ||||||
|         promises.push(AddonCalendar.invalidateEventsList()); |  | ||||||
|         promises.push(AddonCalendar.invalidateAllowedEventTypes()); |  | ||||||
| 
 |  | ||||||
|         if (this.categoriesRetrieved) { |  | ||||||
|             promises.push(CoreCourses.invalidateCategories(0, true)); |  | ||||||
|             this.categoriesRetrieved = false; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         await Promise.all(promises).finally(() => this.fetchData(true, sync, showErrors)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Check date should be shown on event list for the current event. |  | ||||||
|      * If date has changed from previous to current event it should be shown. |  | ||||||
|      * |  | ||||||
|      * @param event Current event where to show the date. |  | ||||||
|      * @param prevEvent Previous event where to compare the date with. |  | ||||||
|      * @return If date has changed and should be shown. |  | ||||||
|      */ |  | ||||||
|     protected showDate(event: AddonCalendarEventToDisplay, prevEvent?: AddonCalendarEventToDisplay): boolean { |  | ||||||
|         if (!prevEvent) { |  | ||||||
|             // First event, show it.
 |  | ||||||
|             return true; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // Check if day has changed.
 |  | ||||||
|         return !moment(event.timestart * 1000).isSame(prevEvent.timestart * 1000, 'day'); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Check if event ends the same date or not. |  | ||||||
|      * |  | ||||||
|      * @param event Event info. |  | ||||||
|      * @return If date has changed and should be shown. |  | ||||||
|      */ |  | ||||||
|     protected endsSameDay(event: AddonCalendarEventToDisplay): boolean { |  | ||||||
|         if (!event.timeduration) { |  | ||||||
|             // No duration.
 |  | ||||||
|             return true; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // Check if day has changed.
 |  | ||||||
|         return moment(event.timestart * 1000).isSame((event.timestart + event.timeduration) * 1000, 'day'); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Show the context menu. |  | ||||||
|      * |  | ||||||
|      * @param event Event. |  | ||||||
|      */ |  | ||||||
|     async openFilter(event: MouseEvent): Promise<void> { |  | ||||||
|         await CoreDomUtils.openPopover({ |  | ||||||
|             component: AddonCalendarFilterPopoverComponent, |  | ||||||
|             componentProps: { |  | ||||||
|                 courses: this.courses, |  | ||||||
|                 filter: this.filter, |  | ||||||
|             }, |  | ||||||
|             event, |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Open page to create/edit an event. |  | ||||||
|      * |  | ||||||
|      * @param eventId Event ID to edit. |  | ||||||
|      */ |  | ||||||
|     openEdit(eventId?: number): void { |  | ||||||
|         this.eventId = undefined; |  | ||||||
|         eventId = eventId || 0; |  | ||||||
| 
 |  | ||||||
|         const params: Params = {}; |  | ||||||
| 
 |  | ||||||
|         if (this.filter.courseId) { |  | ||||||
|             params.courseId = this.filter.courseId; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         CoreNavigator.navigateToSitePath(`calendar/edit/${eventId}`, { params }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Open calendar events settings. |  | ||||||
|      */ |  | ||||||
|     openSettings(): void { |  | ||||||
|         CoreNavigator.navigateToSitePath('/calendar/settings'); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Navigate to a particular event. |  | ||||||
|      * |  | ||||||
|      * @param eventId Event to load. |  | ||||||
|      */ |  | ||||||
|     gotoEvent(eventId: number): void { |  | ||||||
|         this.eventId = eventId; |  | ||||||
| 
 |  | ||||||
|         if (eventId <= 0) { |  | ||||||
|             // It's an offline event, go to the edit page.
 |  | ||||||
|             this.openEdit(eventId); |  | ||||||
|         } else { |  | ||||||
|             CoreNavigator.navigateToSitePath(`/calendar/event/${eventId}`); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Find an event and mark it as deleted. |  | ||||||
|      * |  | ||||||
|      * @param eventId Event ID. |  | ||||||
|      * @param deleted Whether to mark it as deleted or not. |  | ||||||
|      */ |  | ||||||
|     protected markAsDeleted(eventId: number, deleted: boolean): void { |  | ||||||
|         const event = this.onlineEvents.find((event) => event.id == eventId); |  | ||||||
| 
 |  | ||||||
|         if (event) { |  | ||||||
|             event.deleted = deleted; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Page destroyed. |  | ||||||
|      */ |  | ||||||
|     ngOnDestroy(): void { |  | ||||||
|         this.obsDefaultTimeChange?.off(); |  | ||||||
|         this.newEventObserver?.off(); |  | ||||||
|         this.discardedObserver?.off(); |  | ||||||
|         this.editEventObserver?.off(); |  | ||||||
|         this.deleteEventObserver?.off(); |  | ||||||
|         this.undeleteEventObserver?.off(); |  | ||||||
|         this.syncObserver?.off(); |  | ||||||
|         this.manualSyncObserver?.off(); |  | ||||||
|         this.filterChangedObserver?.off(); |  | ||||||
|         this.onlineObserver?.unsubscribe(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
| @ -1,5 +0,0 @@ | |||||||
| :host { |  | ||||||
|     ion-note { |  | ||||||
|         max-width: 30%; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -125,36 +125,6 @@ export class AddonCalendarProvider { | |||||||
|         }, |         }, | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Check if a certain site allows deleting events. |  | ||||||
|      * |  | ||||||
|      * @param siteId Site Id. If not defined, use current site. |  | ||||||
|      * @return Promise resolved with true if can delete. |  | ||||||
|      * @since 3.3 |  | ||||||
|      */ |  | ||||||
|     async canDeleteEvents(siteId?: string): Promise<boolean> { |  | ||||||
|         try { |  | ||||||
|             const site = await CoreSites.getSite(siteId); |  | ||||||
| 
 |  | ||||||
|             return this.canDeleteEventsInSite(site); |  | ||||||
|         } catch { |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Check if a certain site allows deleting events. |  | ||||||
|      * |  | ||||||
|      * @param site Site. If not defined, use current site. |  | ||||||
|      * @return Whether events can be deleted. |  | ||||||
|      * @since 3.3 |  | ||||||
|      */ |  | ||||||
|     canDeleteEventsInSite(site?: CoreSite): boolean { |  | ||||||
|         site = site || CoreSites.getCurrentSite(); |  | ||||||
| 
 |  | ||||||
|         return !!site?.wsAvailable('core_calendar_delete_calendar_events'); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * Check if a certain site allows creating and editing events. |      * Check if a certain site allows creating and editing events. | ||||||
|      * |      * | ||||||
| @ -186,67 +156,6 @@ export class AddonCalendarProvider { | |||||||
|         return !!site?.isVersionGreaterEqualThan('3.7.1'); |         return !!site?.isVersionGreaterEqualThan('3.7.1'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Check if a certain site allows viewing events in monthly view. |  | ||||||
|      * |  | ||||||
|      * @param siteId Site Id. If not defined, use current site. |  | ||||||
|      * @return Promise resolved with true if monthly view is supported. |  | ||||||
|      * @since 3.4 |  | ||||||
|      */ |  | ||||||
|     async canViewMonth(siteId?: string): Promise<boolean> { |  | ||||||
|         try { |  | ||||||
|             const site = await CoreSites.getSite(siteId); |  | ||||||
| 
 |  | ||||||
|             return this.canViewMonthInSite(site); |  | ||||||
|         } catch { |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Check if a certain site allows viewing events in monthly view. |  | ||||||
|      * |  | ||||||
|      * @param site Site. If not defined, use current site. |  | ||||||
|      * @return Whether monthly view is supported. |  | ||||||
|      * @since 3.4 |  | ||||||
|      */ |  | ||||||
|     canViewMonthInSite(site?: CoreSite): boolean { |  | ||||||
|         site = site || CoreSites.getCurrentSite(); |  | ||||||
| 
 |  | ||||||
|         return !!site?.wsAvailable('core_calendar_get_calendar_monthly_view'); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Gets the site main calendar page path. |  | ||||||
|      * |  | ||||||
|      * @param site Site. If not defined, use current site. |  | ||||||
|      * @return Main calendar page path of the site. |  | ||||||
|      */ |  | ||||||
|     getMainCalendarPagePath(site?: CoreSite): string { |  | ||||||
|         return AddonCalendarMainMenuHandlerService.PAGE_NAME + (this.canViewMonthInSite(site) ? '' : '/list'); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Removes expired events from local DB. |  | ||||||
|      * |  | ||||||
|      * @param siteId ID of the site the event belongs to. If not defined, use current site. |  | ||||||
|      * @return Promise resolved when done. |  | ||||||
|      */ |  | ||||||
|     async cleanExpiredEvents(siteId?: string): Promise<void> { |  | ||||||
|         const site = await CoreSites.getSite(siteId); |  | ||||||
|         if (this.canViewMonthInSite(site)) { |  | ||||||
|             // Site supports monthly view, don't clean expired events because user can see past events.
 |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|         const events = await site.getDb().getRecordsSelect<AddonCalendarEventDBRecord>( |  | ||||||
|             EVENTS_TABLE, |  | ||||||
|             'timestart + timeduration < ?', |  | ||||||
|             [CoreTimeUtils.timestamp()], |  | ||||||
|         ); |  | ||||||
| 
 |  | ||||||
|         await Promise.all(events.map((event) => this.deleteLocalEvent(event.id!, siteId))); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * Delete an event. |      * Delete an event. | ||||||
|      * |      * | ||||||
| @ -367,12 +276,8 @@ export class AddonCalendarProvider { | |||||||
|                         return; |                         return; | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                     // Check which page we should load.
 |  | ||||||
|                     const site = await CoreSites.getSite(notification.siteId); |  | ||||||
|                     const pageName = this.getMainCalendarPagePath(site); |  | ||||||
| 
 |  | ||||||
|                     CoreNavigator.navigateToSitePath( |                     CoreNavigator.navigateToSitePath( | ||||||
|                         pageName, |                         AddonCalendarMainMenuHandlerService.PAGE_NAME, | ||||||
|                         { |                         { | ||||||
|                             siteId: notification.siteId, |                             siteId: notification.siteId, | ||||||
|                             preferCurrentTab: false, |                             preferCurrentTab: false, | ||||||
| @ -695,7 +600,6 @@ export class AddonCalendarProvider { | |||||||
|      * @param id Event ID. |      * @param id Event ID. | ||||||
|      * @param siteId ID of the site. If not defined, use current site. |      * @param siteId ID of the site. If not defined, use current site. | ||||||
|      * @return Promise resolved when the event data is retrieved. |      * @return Promise resolved when the event data is retrieved. | ||||||
|      * @since 3.4 |  | ||||||
|      */ |      */ | ||||||
|     async getEventById(id: number, siteId?: string): Promise<AddonCalendarEvent> { |     async getEventById(id: number, siteId?: string): Promise<AddonCalendarEvent> { | ||||||
|         const site = await CoreSites.getSite(siteId); |         const site = await CoreSites.getSite(siteId); | ||||||
| @ -742,10 +646,6 @@ export class AddonCalendarProvider { | |||||||
|         const record: AddonCalendarGetEventsEvent | AddonCalendarEvent | AddonCalendarEventDBRecord = |         const record: AddonCalendarGetEventsEvent | AddonCalendarEvent | AddonCalendarEventDBRecord = | ||||||
|             await site.getDb().getRecord(EVENTS_TABLE, { id: id }); |             await site.getDb().getRecord(EVENTS_TABLE, { id: id }); | ||||||
| 
 | 
 | ||||||
|         if (!this.isGetEventByIdAvailableInSite(site)) { |  | ||||||
|             return record as AddonCalendarGetEventsEvent; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         const eventConverted = record as AddonCalendarEvent; |         const eventConverted = record as AddonCalendarEvent; | ||||||
|         const originalEvent = record as AddonCalendarGetEventsEvent; |         const originalEvent = record as AddonCalendarGetEventsEvent; | ||||||
|         const recordAsRecord = record as AddonCalendarEventDBRecord; |         const recordAsRecord = record as AddonCalendarEventDBRecord; | ||||||
| @ -994,10 +894,6 @@ export class AddonCalendarProvider { | |||||||
|         }; |         }; | ||||||
|         const response: AddonCalendarGetCalendarEventsWSResponse = |         const response: AddonCalendarGetCalendarEventsWSResponse = | ||||||
|             await site.read('core_calendar_get_calendar_events', params, preSets); |             await site.read('core_calendar_get_calendar_events', params, preSets); | ||||||
|         if (!this.canViewMonthInSite(site)) { |  | ||||||
|             // Store events only in 3.1-3.3. In 3.4+ we'll use the new WS that return more info.
 |  | ||||||
|             this.storeEventsInLocalDB(response.events, siteId); |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         return response.events; |         return response.events; | ||||||
|     } |     } | ||||||
| @ -1059,12 +955,8 @@ export class AddonCalendarProvider { | |||||||
|         const params: AddonCalendarGetCalendarMonthlyViewWSParams = { |         const params: AddonCalendarGetCalendarMonthlyViewWSParams = { | ||||||
|             year: year, |             year: year, | ||||||
|             month: month, |             month: month, | ||||||
|  |             mini: true, // Set mini to 1 to prevent returning the course selector HTML.
 | ||||||
|         }; |         }; | ||||||
|         // This parameter requires Moodle 3.5.
 |  | ||||||
|         if (site.isVersionGreaterEqualThan('3.5')) { |  | ||||||
|             // Set mini to 1 to prevent returning the course selector HTML.
 |  | ||||||
|             params.mini = true; |  | ||||||
|         } |  | ||||||
|         if (courseId) { |         if (courseId) { | ||||||
|             params.courseid = courseId; |             params.courseid = courseId; | ||||||
|         } |         } | ||||||
| @ -1406,36 +1298,6 @@ export class AddonCalendarProvider { | |||||||
|         return this.isCalendarDisabledInSite(site); |         return this.isCalendarDisabledInSite(site); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Check if the get event by ID WS is available. |  | ||||||
|      * |  | ||||||
|      * @param siteId Site Id. If not defined, use current site. |  | ||||||
|      * @return Promise resolved with true if available. |  | ||||||
|      * @since 3.4 |  | ||||||
|      */ |  | ||||||
|     async isGetEventByIdAvailable(siteId?: string): Promise<boolean> { |  | ||||||
|         try { |  | ||||||
|             const site = await CoreSites.getSite(siteId); |  | ||||||
| 
 |  | ||||||
|             return this.isGetEventByIdAvailableInSite(site); |  | ||||||
|         } catch { |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Check if the get event by ID WS is available in a certain site. |  | ||||||
|      * |  | ||||||
|      * @param site Site. If not defined, use current site. |  | ||||||
|      * @return Whether it's available. |  | ||||||
|      * @since 3.4 |  | ||||||
|      */ |  | ||||||
|     isGetEventByIdAvailableInSite(site?: CoreSite): boolean { |  | ||||||
|         site = site || CoreSites.getCurrentSite(); |  | ||||||
| 
 |  | ||||||
|         return !!site?.wsAvailable('core_calendar_get_calendar_event_by_id'); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * Get the next events for all the sites and schedules their notifications. |      * Get the next events for all the sites and schedules their notifications. | ||||||
|      * If an event notification time is 0, cancel its scheduled notification (if any). |      * If an event notification time is 0, cancel its scheduled notification (if any). | ||||||
| @ -1450,7 +1312,7 @@ export class AddonCalendarProvider { | |||||||
| 
 | 
 | ||||||
|         const siteIds = await CoreSites.getSitesIds(); |         const siteIds = await CoreSites.getSitesIds(); | ||||||
| 
 | 
 | ||||||
|         const promises = siteIds.map((siteId: string) => this.cleanExpiredEvents(siteId).then(async() => { |         const promises = siteIds.map((siteId: string) => async () => { | ||||||
|             if (notificationsEnabled) { |             if (notificationsEnabled) { | ||||||
|                 // Check if calendar is disabled for the site.
 |                 // Check if calendar is disabled for the site.
 | ||||||
|                 const disabled = await this.isDisabled(siteId); |                 const disabled = await this.isDisabled(siteId); | ||||||
| @ -1462,7 +1324,7 @@ export class AddonCalendarProvider { | |||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             return; |             return; | ||||||
|         })); |         }); | ||||||
| 
 | 
 | ||||||
|         await Promise.all(promises); |         await Promise.all(promises); | ||||||
|     } |     } | ||||||
| @ -1995,7 +1857,7 @@ export type AddonCalendarMonth = { | |||||||
|     date: CoreWSDate; |     date: CoreWSDate; | ||||||
|     periodname: string; // Periodname.
 |     periodname: string; // Periodname.
 | ||||||
|     includenavigation: boolean; // Includenavigation.
 |     includenavigation: boolean; // Includenavigation.
 | ||||||
|     initialeventsloaded: boolean; // @since 3.5. Initialeventsloaded.
 |     initialeventsloaded: boolean; // Initialeventsloaded.
 | ||||||
|     previousperiod: CoreWSDate; |     previousperiod: CoreWSDate; | ||||||
|     previousperiodlink: string; // Previousperiodlink.
 |     previousperiodlink: string; // Previousperiodlink.
 | ||||||
|     previousperiodname: string; // Previousperiodname.
 |     previousperiodname: string; // Previousperiodname.
 | ||||||
| @ -2151,7 +2013,7 @@ export type AddonCalendarGetEventsEvent = { | |||||||
|     description?: string; // Description.
 |     description?: string; // Description.
 | ||||||
|     format: number; // Description format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 |     format: number; // Description format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||||
|     courseid: number; // Course id.
 |     courseid: number; // Course id.
 | ||||||
|     categoryid?: number; // @since 3.4. Category id (only for category events).
 |     categoryid?: number; // Category id (only for category events).
 | ||||||
|     groupid: number; // Group id.
 |     groupid: number; // Group id.
 | ||||||
|     userid: number; // User id.
 |     userid: number; // User id.
 | ||||||
|     repeatid: number; // Repeat id.
 |     repeatid: number; // Repeat id.
 | ||||||
|  | |||||||
| @ -46,7 +46,7 @@ export class AddonCalendarMainMenuHandlerService implements CoreMainMenuHandler | |||||||
|         return { |         return { | ||||||
|             icon: 'far-calendar', |             icon: 'far-calendar', | ||||||
|             title: 'addon.calendar.calendar', |             title: 'addon.calendar.calendar', | ||||||
|             page: AddonCalendar.getMainCalendarPagePath(), |             page: AddonCalendarMainMenuHandlerService.PAGE_NAME, | ||||||
|             class: 'addon-calendar-handler', |             class: 'addon-calendar-handler', | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -111,13 +111,9 @@ export class AddonCalendarViewLinkHandlerService extends CoreContentLinksHandler | |||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return AddonCalendar.isDisabled(siteId).then((disabled) => { |         const disabled = await AddonCalendar.isDisabled(siteId); | ||||||
|             if (disabled) { |  | ||||||
|                 return false; |  | ||||||
|             } |  | ||||||
| 
 | 
 | ||||||
|             return AddonCalendar.canViewMonth(siteId); |         return !disabled; | ||||||
|         }); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -125,17 +125,6 @@ export class AddonMessageOutputAirnotifierProvider { | |||||||
|         return site.invalidateWsCacheForKey(this.getUserDevicesCacheKey()); |         return site.invalidateWsCacheForKey(this.getUserDevicesCacheKey()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Returns whether or not the plugin is enabled for the current site. |  | ||||||
|      * |  | ||||||
|      * @return True if enabled, false otherwise. |  | ||||||
|      * @since 3.2 |  | ||||||
|      */ |  | ||||||
|     isEnabled(): boolean { |  | ||||||
|         return CoreSites.wsAvailableInCurrentSite('message_airnotifier_enable_device') && |  | ||||||
|                 CoreSites.wsAvailableInCurrentSite('message_airnotifier_get_user_devices'); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const AddonMessageOutputAirnotifier = makeSingleton(AddonMessageOutputAirnotifierProvider); | export const AddonMessageOutputAirnotifier = makeSingleton(AddonMessageOutputAirnotifierProvider); | ||||||
|  | |||||||
| @ -15,7 +15,6 @@ | |||||||
| import { Injectable } from '@angular/core'; | import { Injectable } from '@angular/core'; | ||||||
| 
 | 
 | ||||||
| import { AddonMessageOutputHandler, AddonMessageOutputHandlerData } from '@addons/messageoutput/services/messageoutput-delegate'; | import { AddonMessageOutputHandler, AddonMessageOutputHandlerData } from '@addons/messageoutput/services/messageoutput-delegate'; | ||||||
| import { AddonMessageOutputAirnotifierProvider } from '../airnotifier'; |  | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -29,15 +28,13 @@ export class AddonMessageOutputAirnotifierHandlerService implements AddonMessage | |||||||
|     name = 'AddonMessageOutputAirnotifier'; |     name = 'AddonMessageOutputAirnotifier'; | ||||||
|     processorName = 'airnotifier'; |     processorName = 'airnotifier'; | ||||||
| 
 | 
 | ||||||
|     constructor(private airnotifierProvider: AddonMessageOutputAirnotifierProvider) {} |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * Whether or not the module is enabled for the site. |      * Whether or not the module is enabled for the site. | ||||||
|      * |      * | ||||||
|      * @return True if enabled, false otherwise. |      * @return True if enabled, false otherwise. | ||||||
|      */ |      */ | ||||||
|     async isEnabled(): Promise<boolean> { |     async isEnabled(): Promise<boolean> { | ||||||
|         return this.airnotifierProvider.isEnabled(); |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -27,12 +27,12 @@ export const AddonMessagesDiscussionRoute: Route = { | |||||||
| function buildRoutes(injector: Injector): Routes { | function buildRoutes(injector: Injector): Routes { | ||||||
|     return [ |     return [ | ||||||
|         { |         { | ||||||
|             path: 'index', // 3.5 or lower.
 |             path: 'index', // 3.5.
 | ||||||
|             loadChildren: () => |             loadChildren: () => | ||||||
|                 import('./pages/discussions-35/discussions.module').then(m => m.AddonMessagesDiscussions35PageModule), |                 import('./pages/discussions-35/discussions.module').then(m => m.AddonMessagesDiscussions35PageModule), | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             path: 'contacts-35', // 3.5 or lower.
 |             path: 'contacts-35', // 3.5.
 | ||||||
|             loadChildren: () => import('./pages/contacts-35/contacts.module').then(m => m.AddonMessagesContacts35PageModule), |             loadChildren: () => import('./pages/contacts-35/contacts.module').then(m => m.AddonMessagesContacts35PageModule), | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|  | |||||||
| @ -747,8 +747,6 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView | |||||||
|      */ |      */ | ||||||
|     protected async markMessagesAsRead(forceMark: boolean): Promise<void> { |     protected async markMessagesAsRead(forceMark: boolean): Promise<void> { | ||||||
|         let readChanged = false; |         let readChanged = false; | ||||||
| 
 |  | ||||||
|         if (AddonMessages.isMarkAllMessagesReadEnabled()) { |  | ||||||
|         let messageUnreadFound = false; |         let messageUnreadFound = false; | ||||||
| 
 | 
 | ||||||
|         // Mark all messages at a time if there is any unread message.
 |         // Mark all messages at a time if there is any unread message.
 | ||||||
| @ -781,25 +779,6 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView | |||||||
| 
 | 
 | ||||||
|             readChanged = true; |             readChanged = true; | ||||||
|         } |         } | ||||||
|         } else { |  | ||||||
|             this.setUnreadLabelPosition(); |  | ||||||
|             const promises: Promise<void>[] = []; |  | ||||||
| 
 |  | ||||||
|             // Mark each message as read one by one.
 |  | ||||||
|             this.messages.forEach((message) => { |  | ||||||
|                 // If the message is unread, call AddonMessages.markMessageRead.
 |  | ||||||
|                 if (message.useridfrom != this.currentUserId && 'read' in message && !message.read) { |  | ||||||
|                     promises.push(AddonMessages.markMessageRead(message.id).then(() => { |  | ||||||
|                         readChanged = true; |  | ||||||
|                         message.read = true; |  | ||||||
| 
 |  | ||||||
|                         return; |  | ||||||
|                     })); |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
| 
 |  | ||||||
|             await Promise.all(promises); |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         if (readChanged) { |         if (readChanged) { | ||||||
|             CoreEvents.trigger(AddonMessagesProvider.READ_CHANGED_EVENT, { |             CoreEvents.trigger(AddonMessagesProvider.READ_CHANGED_EVENT, { | ||||||
|  | |||||||
| @ -17,7 +17,7 @@ | |||||||
|             <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> |             <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||||
|         </ion-refresher> |         </ion-refresher> | ||||||
| 
 | 
 | ||||||
|         <core-search-box *ngIf="search.enabled" (onSubmit)="searchMessage($event)" (onClear)="clearSearch()" |         <core-search-box (onSubmit)="searchMessage($event)" (onClear)="clearSearch()" | ||||||
|             [placeholder]=" 'addon.messages.message' | translate" autocorrect="off" spellcheck="false" lengthCheck="2" |             [placeholder]=" 'addon.messages.message' | translate" autocorrect="off" spellcheck="false" lengthCheck="2" | ||||||
|             [disabled]="!loaded" searchArea="AddonMessagesDiscussions" [autoFocus]="false"></core-search-box> |             [disabled]="!loaded" searchArea="AddonMessagesDiscussions" [autoFocus]="false"></core-search-box> | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -56,7 +56,6 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy { | |||||||
|     discussionUserId?: number; |     discussionUserId?: number; | ||||||
| 
 | 
 | ||||||
|     search = { |     search = { | ||||||
|         enabled: false, |  | ||||||
|         showResults: false, |         showResults: false, | ||||||
|         results: <AddonMessagesMessageAreaContact[]> [], |         results: <AddonMessagesMessageAreaContact[]> [], | ||||||
|         loading: '', |         loading: '', | ||||||
| @ -179,7 +178,6 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy { | |||||||
|      */ |      */ | ||||||
|     protected async fetchData(): Promise<void> { |     protected async fetchData(): Promise<void> { | ||||||
|         this.loadingMessage = this.loadingMessages; |         this.loadingMessage = this.loadingMessages; | ||||||
|         this.search.enabled = AddonMessages.isSearchMessagesEnabled(); |  | ||||||
| 
 | 
 | ||||||
|         const promises: Promise<unknown>[] = []; |         const promises: Promise<unknown>[] = []; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -45,7 +45,7 @@ export class AddonMessagesMainMenuHandlerService implements CoreMainMenuHandler, | |||||||
|         title: 'addon.messages.messages', |         title: 'addon.messages.messages', | ||||||
|         page: AddonMessagesMainMenuHandlerService.PAGE_NAME, |         page: AddonMessagesMainMenuHandlerService.PAGE_NAME, | ||||||
|         class: 'addon-messages-handler', |         class: 'addon-messages-handler', | ||||||
|         showBadge: true, // Do not check isMessageCountEnabled because we'll use fallback it not enabled.
 |         showBadge: true, | ||||||
|         badge: '', |         badge: '', | ||||||
|         badgeA11yText: 'addon.messages.unreadconversations', |         badgeA11yText: 'addon.messages.unreadconversations', | ||||||
|         loading: true, |         loading: true, | ||||||
| @ -199,8 +199,7 @@ export class AddonMessagesMainMenuHandlerService implements CoreMainMenuHandler, | |||||||
|      * @return True if is a sync process, false otherwise. |      * @return True if is a sync process, false otherwise. | ||||||
|      */ |      */ | ||||||
|     isSync(): boolean { |     isSync(): boolean { | ||||||
|         // This is done to use only wifi if using the fallback function.
 |         return false; | ||||||
|         return !AddonMessages.isMessageCountEnabled() && !AddonMessages.isGroupMessagingEnabled(); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -34,9 +34,7 @@ export class AddonMessagesSettingsHandlerService implements CoreSettingsHandler | |||||||
|      * @return Whether or not the handler is enabled on a site level. |      * @return Whether or not the handler is enabled on a site level. | ||||||
|      */ |      */ | ||||||
|     async isEnabled(): Promise<boolean> { |     async isEnabled(): Promise<boolean> { | ||||||
|         const messagingEnabled = await AddonMessages.isPluginEnabled(); |         return await AddonMessages.isPluginEnabled(); | ||||||
| 
 |  | ||||||
|         return messagingEnabled && AddonMessages.isMessagePreferencesEnabled(); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -1567,9 +1567,8 @@ export class AddonMessagesProvider { | |||||||
|                 self: result.types[AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_SELF] || 0, |                 self: result.types[AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_SELF] || 0, | ||||||
|             }; |             }; | ||||||
| 
 | 
 | ||||||
|         } else if (this.isMessageCountEnabled()) { |         } else { | ||||||
|             // @since 3.2
 |             const params: AddonMessageGetUnreadConversationsCountWSParams = { | ||||||
|             const params: { useridto: number } = { |  | ||||||
|                 useridto: site.getUserId(), |                 useridto: site.getUserId(), | ||||||
|             }; |             }; | ||||||
|             const preSets: CoreSiteWSPreSets = { |             const preSets: CoreSiteWSPreSets = { | ||||||
| @ -1577,37 +1576,11 @@ export class AddonMessagesProvider { | |||||||
|                 typeExpected: 'number', |                 typeExpected: 'number', | ||||||
|             }; |             }; | ||||||
| 
 | 
 | ||||||
|             const count: number = await site.read('core_message_get_unread_conversations_count', params, preSets); |             const count = await site.read<number>('core_message_get_unread_conversations_count', params, preSets); | ||||||
| 
 | 
 | ||||||
|             counts = { favourites: 0, individual: count, group: 0, self: 0 }; |             counts = { favourites: 0, individual: count, group: 0, self: 0 }; | ||||||
|         } else { |  | ||||||
|             // Fallback call.
 |  | ||||||
|             const params: AddonMessagesGetMessagesWSParams = { |  | ||||||
|                 read: false, |  | ||||||
|                 limitfrom: 0, |  | ||||||
|                 limitnum: AddonMessagesProvider.LIMIT_MESSAGES + 1, |  | ||||||
|                 useridto: site.getUserId(), |  | ||||||
|                 useridfrom: 0, |  | ||||||
|             }; |  | ||||||
| 
 |  | ||||||
|             const response = await this.getMessages(params, {}, siteId); |  | ||||||
| 
 |  | ||||||
|             // Count the discussions by filtering same senders.
 |  | ||||||
|             const discussions = {}; |  | ||||||
|             response.messages.forEach((message) => { |  | ||||||
|                 discussions[message.useridto] = 1; |  | ||||||
|             }); |  | ||||||
| 
 |  | ||||||
|             const count = Object.keys(discussions).length; |  | ||||||
| 
 |  | ||||||
|             counts = { |  | ||||||
|                 favourites: 0, |  | ||||||
|                 individual: count, |  | ||||||
|                 group: 0, |  | ||||||
|                 self: 0, |  | ||||||
|                 orMore: count > AddonMessagesProvider.LIMIT_MESSAGES, |  | ||||||
|             }; |  | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|         // Notify the new counts so all views are updated.
 |         // Notify the new counts so all views are updated.
 | ||||||
|         CoreEvents.trigger(AddonMessagesProvider.UNREAD_CONVERSATION_COUNTS_EVENT, counts, site.id); |         CoreEvents.trigger(AddonMessagesProvider.UNREAD_CONVERSATION_COUNTS_EVENT, counts, site.id); | ||||||
| 
 | 
 | ||||||
| @ -1936,8 +1909,7 @@ export class AddonMessagesProvider { | |||||||
|             // @since 3.6
 |             // @since 3.6
 | ||||||
|             return site.invalidateWsCacheForKey(this.getCacheKeyForUnreadConversationCounts()); |             return site.invalidateWsCacheForKey(this.getCacheKeyForUnreadConversationCounts()); | ||||||
| 
 | 
 | ||||||
|         } else if (this.isMessageCountEnabled()) { |         } else { | ||||||
|             // @since 3.2
 |  | ||||||
|             return site.invalidateWsCacheForKey(this.getCacheKeyForMessageCount(site.getUserId())); |             return site.invalidateWsCacheForKey(this.getCacheKeyForMessageCount(site.getUserId())); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @ -2016,37 +1988,6 @@ export class AddonMessagesProvider { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Returns whether or not we can mark all messages as read. |  | ||||||
|      * |  | ||||||
|      * @return If related WS is available on current site. |  | ||||||
|      * @since 3.2 |  | ||||||
|      */ |  | ||||||
|     isMarkAllMessagesReadEnabled(): boolean { |  | ||||||
|         return CoreSites.wsAvailableInCurrentSite('core_message_mark_all_conversation_messages_as_read') || |  | ||||||
|                 CoreSites.wsAvailableInCurrentSite('core_message_mark_all_messages_as_read'); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Returns whether or not we can count unread messages. |  | ||||||
|      * |  | ||||||
|      * @return True if enabled, false otherwise. |  | ||||||
|      * @since 3.2 |  | ||||||
|      */ |  | ||||||
|     isMessageCountEnabled(): boolean { |  | ||||||
|         return CoreSites.wsAvailableInCurrentSite('core_message_get_unread_conversations_count'); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Returns whether or not the message preferences are enabled for the current site. |  | ||||||
|      * |  | ||||||
|      * @return True if enabled, false otherwise. |  | ||||||
|      * @since 3.2 |  | ||||||
|      */ |  | ||||||
|     isMessagePreferencesEnabled(): boolean { |  | ||||||
|         return CoreSites.wsAvailableInCurrentSite('core_message_get_user_message_preferences'); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * Returns whether or not messaging is enabled for a certain site. |      * Returns whether or not messaging is enabled for a certain site. | ||||||
|      * |      * | ||||||
| @ -2105,15 +2046,6 @@ export class AddonMessagesProvider { | |||||||
|         return site.canUseAdvancedFeature('messaging'); |         return site.canUseAdvancedFeature('messaging'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Returns whether or not we can search messages. |  | ||||||
|      * |  | ||||||
|      * @since 3.2 |  | ||||||
|      */ |  | ||||||
|     isSearchMessagesEnabled(): boolean { |  | ||||||
|         return CoreSites.wsAvailableInCurrentSite('core_message_data_for_messagearea_search_messages'); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * Returns whether or not self conversation is supported in a certain site. |      * Returns whether or not self conversation is supported in a certain site. | ||||||
|      * |      * | ||||||
| @ -3691,6 +3623,13 @@ type AddonMessagesSetFavouriteConversationsWSParams = { | |||||||
|     conversations: number[]; |     conversations: number[]; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | /** | ||||||
|  |  * Params of core_message_get_unread_conversations_count WS. | ||||||
|  |  */ | ||||||
|  | type AddonMessageGetUnreadConversationsCountWSParams = { | ||||||
|  |     useridto: number; // The user id who received the message, 0 for any user.
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * Data sent by UNREAD_CONVERSATION_COUNTS_EVENT event. |  * Data sent by UNREAD_CONVERSATION_COUNTS_EVENT event. | ||||||
|  */ |  */ | ||||||
|  | |||||||
| @ -96,7 +96,7 @@ | |||||||
|                     <h2 *ngIf="assign.teamsubmission">{{ 'addon.mod_assign.numberofteams' | translate }}</h2> |                     <h2 *ngIf="assign.teamsubmission">{{ 'addon.mod_assign.numberofteams' | translate }}</h2> | ||||||
|                     <h2 *ngIf="!assign.teamsubmission">{{ 'addon.mod_assign.numberofparticipants' | translate }}</h2> |                     <h2 *ngIf="!assign.teamsubmission">{{ 'addon.mod_assign.numberofparticipants' | translate }}</h2> | ||||||
|                 </ion-label> |                 </ion-label> | ||||||
|                 <ion-badge slot="end" *ngIf="showNumbers" color="primary"> |                 <ion-badge slot="end" color="primary"> | ||||||
|                     <span aria-hidden="true">{{ summary.participantcount }}</span> |                     <span aria-hidden="true">{{ summary.participantcount }}</span> | ||||||
|                     <span class="sr-only" *ngIf="!assign.teamsubmission"> |                     <span class="sr-only" *ngIf="!assign.teamsubmission"> | ||||||
|                         {{ 'addon.mod_assign.numberofparticipantscountdescription' | translate:{count: summary.participantcount} }} |                         {{ 'addon.mod_assign.numberofparticipantscountdescription' | translate:{count: summary.participantcount} }} | ||||||
| @ -109,12 +109,12 @@ | |||||||
| 
 | 
 | ||||||
|             <!-- Summary of submissions with draft status. --> |             <!-- Summary of submissions with draft status. --> | ||||||
|             <ion-item class="ion-text-wrap" *ngIf="assign.submissiondrafts && summary && summary.submissionsenabled" |             <ion-item class="ion-text-wrap" *ngIf="assign.submissiondrafts && summary && summary.submissionsenabled" | ||||||
|                 [class.hide-detail]="showNumbers && !summary.submissiondraftscount" |                 [class.hide-detail]="!summary.submissiondraftscount" | ||||||
|                 detail="true" |                 detail="true" | ||||||
|                 [button]="!showNumbers || summary.submissiondraftscount" |                 [button]="summary.submissiondraftscount" | ||||||
|                 (click)="goToSubmissionList(submissionStatusDraft, !!summary.submissiondraftscount)"> |                 (click)="goToSubmissionList(submissionStatusDraft, !!summary.submissiondraftscount)"> | ||||||
|                 <ion-label><h2>{{ 'addon.mod_assign.numberofdraftsubmissions' | translate }}</h2></ion-label> |                 <ion-label><h2>{{ 'addon.mod_assign.numberofdraftsubmissions' | translate }}</h2></ion-label> | ||||||
|                 <ion-badge slot="end" *ngIf="showNumbers" color="primary"> |                 <ion-badge slot="end" color="primary"> | ||||||
|                     <span aria-hidden="true">{{ summary.submissiondraftscount }}</span> |                     <span aria-hidden="true">{{ summary.submissiondraftscount }}</span> | ||||||
|                     <span class="sr-only"> |                     <span class="sr-only"> | ||||||
|                         {{ 'addon.mod_assign.numberofdraftsubmissionscountdescription' | translate: |                         {{ 'addon.mod_assign.numberofdraftsubmissionscountdescription' | translate: | ||||||
| @ -125,12 +125,12 @@ | |||||||
| 
 | 
 | ||||||
|             <!-- Summary of submissions with submitted status. --> |             <!-- Summary of submissions with submitted status. --> | ||||||
|             <ion-item class="ion-text-wrap" *ngIf="summary && summary.submissionsenabled" |             <ion-item class="ion-text-wrap" *ngIf="summary && summary.submissionsenabled" | ||||||
|                 [class.hide-detail]="showNumbers && !summary.submissionssubmittedcount" |                 [class.hide-detail]="!summary.submissionssubmittedcount" | ||||||
|                 detail="true" |                 detail="true" | ||||||
|                 [button]="!showNumbers || summary.submissionssubmittedcount" |                 [button]="summary.submissionssubmittedcount" | ||||||
|                 (click)="goToSubmissionList(submissionStatusSubmitted, !!summary.submissionssubmittedcount)"> |                 (click)="goToSubmissionList(submissionStatusSubmitted, !!summary.submissionssubmittedcount)"> | ||||||
|                 <ion-label><h2>{{ 'addon.mod_assign.numberofsubmittedassignments' | translate }}</h2></ion-label> |                 <ion-label><h2>{{ 'addon.mod_assign.numberofsubmittedassignments' | translate }}</h2></ion-label> | ||||||
|                 <ion-badge slot="end" *ngIf="showNumbers" color="primary"> |                 <ion-badge slot="end" color="primary"> | ||||||
|                     <span aria-hidden="true">{{ summary.submissionssubmittedcount }}</span> |                     <span aria-hidden="true">{{ summary.submissionssubmittedcount }}</span> | ||||||
|                     <span class="sr-only"> |                     <span class="sr-only"> | ||||||
|                         {{ 'addon.mod_assign.numberofsubmittedassignmentscountdescription' | translate: |                         {{ 'addon.mod_assign.numberofsubmittedassignmentscountdescription' | translate: | ||||||
| @ -140,7 +140,7 @@ | |||||||
|             </ion-item> |             </ion-item> | ||||||
| 
 | 
 | ||||||
|             <!-- Summary of submissions that need grading. --> |             <!-- Summary of submissions that need grading. --> | ||||||
|             <ion-item class="ion-text-wrap" *ngIf="summary && summary.submissionsenabled && !assign.teamsubmission && showNumbers" |             <ion-item class="ion-text-wrap" *ngIf="summary && summary.submissionsenabled && !assign.teamsubmission" | ||||||
|                 [class.hide-detail]="!needsGradingAvailable" |                 [class.hide-detail]="!needsGradingAvailable" | ||||||
|                 detail="true" |                 detail="true" | ||||||
|                 [button]="needsGradingAvailable" |                 [button]="needsGradingAvailable" | ||||||
|  | |||||||
| @ -63,7 +63,6 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo | |||||||
|     canViewOwnSubmission = false; // Whether the user can view their own submission.
 |     canViewOwnSubmission = false; // Whether the user can view their own submission.
 | ||||||
|     timeRemaining?: string; // Message about time remaining to submit.
 |     timeRemaining?: string; // Message about time remaining to submit.
 | ||||||
|     lateSubmissions?: string; // Message about late submissions.
 |     lateSubmissions?: string; // Message about late submissions.
 | ||||||
|     showNumbers = true; // Whether to show number of submissions with each status.
 |  | ||||||
|     summary?: AddonModAssignSubmissionGradingSummary; // The grading summary.
 |     summary?: AddonModAssignSubmissionGradingSummary; // The grading summary.
 | ||||||
|     needsGradingAvailable = false; // Whether we can see the submissions that need grading.
 |     needsGradingAvailable = false; // Whether we can see the submissions that need grading.
 | ||||||
| 
 | 
 | ||||||
| @ -235,8 +234,6 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo | |||||||
| 
 | 
 | ||||||
|                 // Check if groupmode is enabled to avoid showing wrong numbers.
 |                 // Check if groupmode is enabled to avoid showing wrong numbers.
 | ||||||
|                 this.groupInfo = await CoreGroups.getActivityGroupInfo(this.assign.cmid, false); |                 this.groupInfo = await CoreGroups.getActivityGroupInfo(this.assign.cmid, false); | ||||||
|                 this.showNumbers = (this.groupInfo.groups && this.groupInfo.groups.length == 0) || |  | ||||||
|                     this.currentSite!.isVersionGreaterEqualThan('3.5'); |  | ||||||
| 
 | 
 | ||||||
|                 await this.setGroup(CoreGroups.validateGroupId(this.group, this.groupInfo)); |                 await this.setGroup(CoreGroups.validateGroupId(this.group, this.groupInfo)); | ||||||
| 
 | 
 | ||||||
| @ -296,9 +293,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         this.needsGradingAvailable = |         this.needsGradingAvailable = (submissionStatus.gradingsummary?.submissionsneedgradingcount || 0) > 0; | ||||||
|             (submissionStatus.gradingsummary?.submissionsneedgradingcount || 0) > 0 && |  | ||||||
|             this.currentSite!.isVersionGreaterEqualThan('3.2'); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -308,7 +303,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo | |||||||
|      * @param hasSubmissions If the status has any submission. |      * @param hasSubmissions If the status has any submission. | ||||||
|      */ |      */ | ||||||
|     goToSubmissionList(status?: string, hasSubmissions = false): void { |     goToSubmissionList(status?: string, hasSubmissions = false): void { | ||||||
|         if (typeof status != 'undefined' && !hasSubmissions && this.showNumbers) { |         if (typeof status != 'undefined' && !hasSubmissions) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -649,7 +649,6 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can | |||||||
|         this.gradeInfo = await CoreCourse.getModuleBasicGradeInfo(this.moduleId); |         this.gradeInfo = await CoreCourse.getModuleBasicGradeInfo(this.moduleId); | ||||||
| 
 | 
 | ||||||
|         if (!this.gradeInfo) { |         if (!this.gradeInfo) { | ||||||
|             // It won't get gradeinfo on 3.1.
 |  | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -966,7 +965,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Treat outcomes.
 |         // Treat outcomes.
 | ||||||
|         if (this.gradeInfo.outcomes && AddonModAssign.isOutcomesEditEnabled()) { |         if (this.gradeInfo.outcomes) { | ||||||
|             this.gradeInfo.outcomes.forEach((outcome) => { |             this.gradeInfo.outcomes.forEach((outcome) => { | ||||||
|                 if (outcome.scale) { |                 if (outcome.scale) { | ||||||
|                     outcome.options = |                     outcome.options = | ||||||
| @ -981,8 +980,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Get grade items.
 |         // Get grade items.
 | ||||||
|         const grades = <CoreGradesFormattedItem[]> |         const grades = await CoreGradesHelper.getGradeModuleItems(this.courseId, this.moduleId, this.submitId); | ||||||
|             await CoreGradesHelper.getGradeModuleItems(this.courseId, this.moduleId, this.submitId); |  | ||||||
| 
 | 
 | ||||||
|         const outcomes: AddonModAssignGradeOutcome[] = []; |         const outcomes: AddonModAssignGradeOutcome[] = []; | ||||||
| 
 | 
 | ||||||
| @ -1091,16 +1089,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can | |||||||
|                 submissionStatus.lastattempt.submissiongroupmemberswhoneedtosubmit |                 submissionStatus.lastattempt.submissiongroupmemberswhoneedtosubmit | ||||||
|             ) { |             ) { | ||||||
|                 submissionStatus.lastattempt.submissiongroupmemberswhoneedtosubmit.forEach((member) => { |                 submissionStatus.lastattempt.submissiongroupmemberswhoneedtosubmit.forEach((member) => { | ||||||
|                     if (this.blindMarking) { |                     if (!this.blindMarking) { | ||||||
|                         // Users not blinded! (Moodle < 3.1.1, 3.2).
 |  | ||||||
|                         promises.push(AddonModAssign.getAssignmentUserMappings(this.assign!.id, member, { |  | ||||||
|                             cmId: this.moduleId, |  | ||||||
|                         }).then((blindId) => { |  | ||||||
|                             this.membersToSubmitBlind.push(blindId); |  | ||||||
| 
 |  | ||||||
|                             return; |  | ||||||
|                         })); |  | ||||||
|                     } else { |  | ||||||
|                         promises.push(CoreUser.getProfile(member, this.courseId).then((profile) => { |                         promises.push(CoreUser.getProfile(member, this.courseId).then((profile) => { | ||||||
|                             this.membersToSubmit.push(profile); |                             this.membersToSubmit.push(profile); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -72,13 +72,6 @@ | |||||||
|                         </ion-label> |                         </ion-label> | ||||||
|                     </ion-item> |                     </ion-item> | ||||||
|                 </ng-container> |                 </ng-container> | ||||||
| 
 |  | ||||||
|                 <ion-card class="ion-text-wrap core-warning-card" *ngIf="!haveAllParticipants"> |  | ||||||
|                     <ion-item> |  | ||||||
|                         <ion-icon name="fas-exclamation-triangle" slot="start" aria-hidden="true"></ion-icon> |  | ||||||
|                         <ion-label>{{ 'addon.mod_assign.notallparticipantsareshown' | translate }}</ion-label> |  | ||||||
|                     </ion-item> |  | ||||||
|                 </ion-card> |  | ||||||
|             </ion-list> |             </ion-list> | ||||||
|         </core-loading> |         </core-loading> | ||||||
|     </core-split-view> |     </core-split-view> | ||||||
|  | |||||||
| @ -55,7 +55,6 @@ export class AddonModAssignSubmissionListPage implements AfterViewInit, OnDestro | |||||||
|     assign?: AddonModAssignAssign; // Assignment.
 |     assign?: AddonModAssignAssign; // Assignment.
 | ||||||
|     submissions: AddonModAssignSubmissionListManager; // List of submissions
 |     submissions: AddonModAssignSubmissionListManager; // List of submissions
 | ||||||
|     loaded = false; // Whether data has been loaded.
 |     loaded = false; // Whether data has been loaded.
 | ||||||
|     haveAllParticipants = true; // Whether all participants have been loaded.
 |  | ||||||
|     groupId = 0; // Group ID to show.
 |     groupId = 0; // Group ID to show.
 | ||||||
|     courseId!: number; // Course ID the assignment belongs to.
 |     courseId!: number; // Course ID the assignment belongs to.
 | ||||||
|     moduleId!: number; // Module ID the submission belongs to.
 |     moduleId!: number; // Module ID the submission belongs to.
 | ||||||
| @ -202,16 +201,6 @@ export class AddonModAssignSubmissionListPage implements AfterViewInit, OnDestro | |||||||
|     async setGroup(groupId: number): Promise<void> { |     async setGroup(groupId: number): Promise<void> { | ||||||
|         this.groupId = groupId; |         this.groupId = groupId; | ||||||
| 
 | 
 | ||||||
|         this.haveAllParticipants = true; |  | ||||||
| 
 |  | ||||||
|         if (!CoreSites.getCurrentSite()?.wsAvailable('mod_assign_list_participants')) { |  | ||||||
|             // Submissions are not displayed in Moodle 3.1 without the local plugin, see MOBILE-2968.
 |  | ||||||
|             this.haveAllParticipants = false; |  | ||||||
|             this.submissions.resetItems(); |  | ||||||
| 
 |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // Fetch submissions and grades.
 |         // Fetch submissions and grades.
 | ||||||
|         const submissions = |         const submissions = | ||||||
|             await AddonModAssignHelper.getSubmissionsUserData( |             await AddonModAssignHelper.getSubmissionsUserData( | ||||||
|  | |||||||
| @ -398,13 +398,9 @@ export class AddonModAssignHelperProvider { | |||||||
|         groupId?: number, |         groupId?: number, | ||||||
|         options: CoreSitesCommonWSOptions = {}, |         options: CoreSitesCommonWSOptions = {}, | ||||||
|     ): Promise<AddonModAssignSubmissionFormatted[]> { |     ): Promise<AddonModAssignSubmissionFormatted[]> { | ||||||
|         // Create new options including all existing ones.
 |  | ||||||
|         const modOptions: CoreCourseCommonModWSOptions = { cmId: assign.cmid, ...options }; |  | ||||||
| 
 |  | ||||||
|         const parts = await this.getParticipants(assign, groupId, options); |         const parts = await this.getParticipants(assign, groupId, options); | ||||||
| 
 | 
 | ||||||
|         const blind = assign.blindmarking && !assign.revealidentities; |         const blind = assign.blindmarking && !assign.revealidentities; | ||||||
|         const promises: Promise<void>[] = []; |  | ||||||
|         const result: AddonModAssignSubmissionFormatted[] = []; |         const result: AddonModAssignSubmissionFormatted[] = []; | ||||||
|         const participants: {[id: number]: AddonModAssignParticipant} = CoreUtils.arrayToObject(parts, 'id'); |         const participants: {[id: number]: AddonModAssignParticipant} = CoreUtils.arrayToObject(parts, 'id'); | ||||||
| 
 | 
 | ||||||
| @ -434,31 +430,12 @@ export class AddonModAssignHelperProvider { | |||||||
|                 submission.groupname = participant.groupname; |                 submission.groupname = participant.groupname; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             let promise = Promise.resolve(); |  | ||||||
|             if (submission.userid && submission.userid > 0 && blind) { |  | ||||||
|                 // Blind but not blinded! (Moodle < 3.1.1, 3.2).
 |  | ||||||
|                 delete submission.userid; |  | ||||||
| 
 |  | ||||||
|                 promise = AddonModAssign.getAssignmentUserMappings(assign.id, submission.submitid, modOptions) |  | ||||||
|                     .then((blindId) => { |  | ||||||
|                         submission.blindid = blindId; |  | ||||||
| 
 |  | ||||||
|                         return; |  | ||||||
|                     }); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             promises.push(promise.then(() => { |  | ||||||
|             // Add to the list.
 |             // Add to the list.
 | ||||||
|             if (submission.userfullname || submission.blindid) { |             if (submission.userfullname || submission.blindid) { | ||||||
|                 result.push(submission); |                 result.push(submission); | ||||||
|             } |             } | ||||||
| 
 |  | ||||||
|                 return; |  | ||||||
|             })); |  | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         await Promise.all(promises); |  | ||||||
| 
 |  | ||||||
|         // Create a submission for each participant left in the list (the participants already treated were removed).
 |         // Create a submission for each participant left in the list (the participants already treated were removed).
 | ||||||
|         CoreUtils.objectToArray(participants).forEach((participant: AddonModAssignParticipant) => { |         CoreUtils.objectToArray(participants).forEach((participant: AddonModAssignParticipant) => { | ||||||
|             const submission = this.createEmptySubmission(); |             const submission = this.createEmptySubmission(); | ||||||
|  | |||||||
| @ -37,7 +37,7 @@ import { CoreCourseLogHelper } from '@features/course/services/log-helper'; | |||||||
| import { CoreUtils } from '@services/utils/utils'; | import { CoreUtils } from '@services/utils/utils'; | ||||||
| import { CoreApp } from '@services/app'; | import { CoreApp } from '@services/app'; | ||||||
| import { CoreNetworkError } from '@classes/errors/network-error'; | import { CoreNetworkError } from '@classes/errors/network-error'; | ||||||
| import { CoreGradesFormattedItem, CoreGradesFormattedRow, CoreGradesHelper } from '@features/grades/services/grades-helper'; | import { CoreGradesFormattedItem, CoreGradesHelper } from '@features/grades/services/grades-helper'; | ||||||
| import { AddonModAssignSubmissionDelegate } from './submission-delegate'; | import { AddonModAssignSubmissionDelegate } from './submission-delegate'; | ||||||
| import { AddonModAssignFeedbackDelegate } from './feedback-delegate'; | import { AddonModAssignFeedbackDelegate } from './feedback-delegate'; | ||||||
| 
 | 
 | ||||||
| @ -457,21 +457,20 @@ export class AddonModAssignSyncProvider extends CoreCourseActivitySyncBaseProvid | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // If grade has been modified from gradebook, do not use offline.
 |         // If grade has been modified from gradebook, do not use offline.
 | ||||||
|         const grades: CoreGradesFormattedItem[] | CoreGradesFormattedRow[] = |         const grades = await CoreGradesHelper.getGradeModuleItems(courseId, assign.cmid, userId, undefined, siteId, true); | ||||||
|             await CoreGradesHelper.getGradeModuleItems(courseId, assign.cmid, userId, undefined, siteId, true); |  | ||||||
| 
 | 
 | ||||||
|         const gradeInfo = await CoreCourse.getModuleBasicGradeInfo(assign.cmid, siteId); |         const gradeInfo = await CoreCourse.getModuleBasicGradeInfo(assign.cmid, siteId); | ||||||
| 
 | 
 | ||||||
|         // Override offline grade and outcomes based on the gradebook data.
 |         // Override offline grade and outcomes based on the gradebook data.
 | ||||||
|         grades.forEach((grade: CoreGradesFormattedItem | CoreGradesFormattedRow) => { |         grades.forEach((grade: CoreGradesFormattedItem) => { | ||||||
|             if ('gradedategraded' in grade && (grade.gradedategraded || 0) >= offlineData.timemodified) { |             if ((grade.gradedategraded || 0) >= offlineData.timemodified) { | ||||||
|                 if (!grade.outcomeid && !grade.scaleid) { |                 if (!grade.outcomeid && !grade.scaleid) { | ||||||
|                     if (gradeInfo && gradeInfo.scale) { |                     if (gradeInfo && gradeInfo.scale) { | ||||||
|                         offlineData.grade = this.getSelectedScaleId(gradeInfo.scale, grade.grade || ''); |                         offlineData.grade = this.getSelectedScaleId(gradeInfo.scale, grade.grade || ''); | ||||||
|                     } else { |                     } else { | ||||||
|                         offlineData.grade = parseFloat(grade.grade || ''); |                         offlineData.grade = parseFloat(grade.grade || ''); | ||||||
|                     } |                     } | ||||||
|                 } else if (gradeInfo && grade.outcomeid && AddonModAssign.isOutcomesEditEnabled() && gradeInfo.outcomes) { |                 } else if (gradeInfo && grade.outcomeid && gradeInfo.outcomes) { | ||||||
|                     gradeInfo.outcomes.forEach((outcome, index) => { |                     gradeInfo.outcomes.forEach((outcome, index) => { | ||||||
|                         if (outcome.scale && grade.itemnumber == index) { |                         if (outcome.scale && grade.itemnumber == index) { | ||||||
|                             offlineData.outcomes[grade.itemnumber] = this.getSelectedScaleId( |                             offlineData.outcomes[grade.itemnumber] = this.getSelectedScaleId( | ||||||
|  | |||||||
| @ -90,8 +90,6 @@ export class AddonModAssignProvider { | |||||||
|     static readonly SUBMITTED_FOR_GRADING_EVENT = 'addon_mod_assign_submitted_for_grading'; |     static readonly SUBMITTED_FOR_GRADING_EVENT = 'addon_mod_assign_submitted_for_grading'; | ||||||
|     static readonly GRADED_EVENT = 'addon_mod_assign_graded'; |     static readonly GRADED_EVENT = 'addon_mod_assign_graded'; | ||||||
| 
 | 
 | ||||||
|     protected gradingOfflineEnabled: {[siteId: string]: boolean} = {}; |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * Check if the user can submit in offline. This should only be used if submissionStatus.lastattempt.cansubmit cannot |      * Check if the user can submit in offline. This should only be used if submissionStatus.lastattempt.cansubmit cannot | ||||||
|      * be used (offline usage). |      * be used (offline usage). | ||||||
| @ -151,7 +149,7 @@ export class AddonModAssignProvider { | |||||||
| 
 | 
 | ||||||
|         return { |         return { | ||||||
|             isBlind: !userId ? false : !!isBlind, |             isBlind: !userId ? false : !!isBlind, | ||||||
|             groupId: site.isVersionGreaterEqualThan('3.5') ? groupId || 0 : 0, |             groupId: groupId || 0, | ||||||
|             userId: userId || site.getUserId(), |             userId: userId || site.getUserId(), | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
| @ -662,10 +660,6 @@ export class AddonModAssignProvider { | |||||||
|         groupId = groupId || 0; |         groupId = groupId || 0; | ||||||
| 
 | 
 | ||||||
|         const site = await CoreSites.getSite(options.siteId); |         const site = await CoreSites.getSite(options.siteId); | ||||||
|         if (!site.wsAvailable('mod_assign_list_participants')) { |  | ||||||
|             // Silently fail if is not available. (needs Moodle version >= 3.2)
 |  | ||||||
|             throw new CoreError('mod_assign_list_participants WS is only available 3.2 onwards'); |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         const params: AddonModAssignListParticipantsWSParams = { |         const params: AddonModAssignListParticipantsWSParams = { | ||||||
|             assignid: assignId, |             assignid: assignId, | ||||||
| @ -836,47 +830,6 @@ export class AddonModAssignProvider { | |||||||
|         await site.invalidateWsCacheForKeyStartingWith(this.listParticipantsPrefixCacheKey(assignId)); |         await site.invalidateWsCacheForKeyStartingWith(this.listParticipantsPrefixCacheKey(assignId)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Convenience function to check if grading offline is enabled. |  | ||||||
|      * |  | ||||||
|      * @param siteId Site ID. If not defined, current site. |  | ||||||
|      * @return Promise resolved with boolean: whether grading offline is enabled. |  | ||||||
|      */ |  | ||||||
|     protected async isGradingOfflineEnabled(siteId?: string): Promise<boolean> { |  | ||||||
|         siteId = siteId || CoreSites.getCurrentSiteId(); |  | ||||||
| 
 |  | ||||||
|         if (typeof this.gradingOfflineEnabled[siteId] != 'undefined') { |  | ||||||
|             return this.gradingOfflineEnabled[siteId]; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         this.gradingOfflineEnabled[siteId] = await CoreGrades.isGradeItemsAvailable(siteId); |  | ||||||
| 
 |  | ||||||
|         return this.gradingOfflineEnabled[siteId]; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Outcomes only can be edited if mod_assign_submit_grading_form is available. |  | ||||||
|      * |  | ||||||
|      * @param siteId Site ID. If not defined, current site. |  | ||||||
|      * @return Promise resolved with true if outcomes edit is enabled, rejected or resolved with false otherwise. |  | ||||||
|      * @since 3.2 |  | ||||||
|      */ |  | ||||||
|     async isOutcomesEditEnabled(siteId?: string): Promise<boolean> { |  | ||||||
|         const site = await CoreSites.getSite(siteId); |  | ||||||
| 
 |  | ||||||
|         return site.wsAvailable('mod_assign_submit_grading_form'); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Check if assignments plugin is enabled in a certain site. |  | ||||||
|      * |  | ||||||
|      * @param siteId Site ID. If not defined, current site. |  | ||||||
|      * @return Whether the plugin is enabled. |  | ||||||
|      */ |  | ||||||
|     isPluginEnabled(): boolean { |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * Check if a submission is open. This function is based on Moodle's submissions_open. |      * Check if a submission is open. This function is based on Moodle's submissions_open. | ||||||
|      * |      * | ||||||
| @ -1261,25 +1214,6 @@ export class AddonModAssignProvider { | |||||||
|             return false; |             return false; | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         // Grading offline is only allowed if WS of grade items is enabled to avoid inconsistency.
 |  | ||||||
|         const enabled = await this.isGradingOfflineEnabled(siteId); |  | ||||||
|         if (!enabled) { |  | ||||||
|             await this.submitGradingFormOnline( |  | ||||||
|                 assignId, |  | ||||||
|                 userId, |  | ||||||
|                 grade, |  | ||||||
|                 attemptNumber, |  | ||||||
|                 addAttempt, |  | ||||||
|                 workflowState, |  | ||||||
|                 applyToAll, |  | ||||||
|                 outcomes, |  | ||||||
|                 pluginData, |  | ||||||
|                 siteId, |  | ||||||
|             ); |  | ||||||
| 
 |  | ||||||
|             return true; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if (!CoreApp.isOnline()) { |         if (!CoreApp.isOnline()) { | ||||||
|             // App is offline, store the action.
 |             // App is offline, store the action.
 | ||||||
|             return storeOffline(); |             return storeOffline(); | ||||||
| @ -1345,9 +1279,6 @@ export class AddonModAssignProvider { | |||||||
|         const site = await CoreSites.getSite(siteId); |         const site = await CoreSites.getSite(siteId); | ||||||
|         userId = userId || site.getUserId(); |         userId = userId || site.getUserId(); | ||||||
| 
 | 
 | ||||||
|         if (site.wsAvailable('mod_assign_submit_grading_form')) { |  | ||||||
|             // WS available @since 3.2.
 |  | ||||||
| 
 |  | ||||||
|         const jsonData = { |         const jsonData = { | ||||||
|             grade, |             grade, | ||||||
|             attemptnumber: attemptNumber, |             attemptnumber: attemptNumber, | ||||||
| @ -1377,26 +1308,6 @@ export class AddonModAssignProvider { | |||||||
|             // The WebService returned warnings, reject.
 |             // The WebService returned warnings, reject.
 | ||||||
|             throw new CoreWSError(warnings[0]); |             throw new CoreWSError(warnings[0]); | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // WS not available, fallback to save_grade.
 |  | ||||||
|         const params: AddonModAssignSaveGradeWSParams = { |  | ||||||
|             assignmentid: assignId, |  | ||||||
|             userid: userId, |  | ||||||
|             grade: grade, |  | ||||||
|             attemptnumber: attemptNumber, |  | ||||||
|             addattempt: addAttempt, |  | ||||||
|             workflowstate: workflowState, |  | ||||||
|             applytoall: applyToAll, |  | ||||||
|             plugindata: pluginData, |  | ||||||
|         }; |  | ||||||
|         const preSets: CoreSiteWSPreSets = { |  | ||||||
|             responseExpected: false, |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         await site.write('mod_assign_save_grade', params, preSets); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
| @ -1431,7 +1342,7 @@ export type AddonModAssignAssign = { | |||||||
|     timemodified: number; // Last time assignment was modified.
 |     timemodified: number; // Last time assignment was modified.
 | ||||||
|     completionsubmit: number; // If enabled, set activity as complete following submission.
 |     completionsubmit: number; // If enabled, set activity as complete following submission.
 | ||||||
|     cutoffdate: number; // Date after which submission is not accepted without an extension.
 |     cutoffdate: number; // Date after which submission is not accepted without an extension.
 | ||||||
|     gradingduedate?: number; // @since 3.3. The expected date for marking the submissions.
 |     gradingduedate?: number; // The expected date for marking the submissions.
 | ||||||
|     teamsubmission: number; // If enabled, students submit as a team.
 |     teamsubmission: number; // If enabled, students submit as a team.
 | ||||||
|     requireallteammemberssubmit: number; // If enabled, all team members must submit.
 |     requireallteammemberssubmit: number; // If enabled, all team members must submit.
 | ||||||
|     teamsubmissiongroupingid: number; // The grouping id for the team submission groups.
 |     teamsubmissiongroupingid: number; // The grouping id for the team submission groups.
 | ||||||
| @ -1443,13 +1354,13 @@ export type AddonModAssignAssign = { | |||||||
|     markingworkflow: number; // Enable marking workflow.
 |     markingworkflow: number; // Enable marking workflow.
 | ||||||
|     markingallocation: number; // Enable marking allocation.
 |     markingallocation: number; // Enable marking allocation.
 | ||||||
|     requiresubmissionstatement: number; // Student must accept submission statement.
 |     requiresubmissionstatement: number; // Student must accept submission statement.
 | ||||||
|     preventsubmissionnotingroup?: number; // @since 3.2. Prevent submission not in group.
 |     preventsubmissionnotingroup?: number; // Prevent submission not in group.
 | ||||||
|     submissionstatement?: string; // @since 3.2. Submission statement formatted.
 |     submissionstatement?: string; // Submission statement formatted.
 | ||||||
|     submissionstatementformat?: number; // @since 3.2. Submissionstatement format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 |     submissionstatementformat?: number; // Submissionstatement format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||||
|     configs: AddonModAssignConfig[]; // Configuration settings.
 |     configs: AddonModAssignConfig[]; // Configuration settings.
 | ||||||
|     intro?: string; // Assignment intro, not allways returned because it deppends on the activity configuration.
 |     intro?: string; // Assignment intro, not allways returned because it deppends on the activity configuration.
 | ||||||
|     introformat?: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 |     introformat?: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||||
|     introfiles?: CoreWSExternalFile[]; // @since 3.2.
 |     introfiles?: CoreWSExternalFile[]; | ||||||
|     introattachments?: CoreWSExternalFile[]; |     introattachments?: CoreWSExternalFile[]; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| @ -1494,7 +1405,7 @@ export type AddonModAssignSubmission = { | |||||||
|     assignment?: number; // Assignment id.
 |     assignment?: number; // Assignment id.
 | ||||||
|     latest?: number; // Latest attempt.
 |     latest?: number; // Latest attempt.
 | ||||||
|     plugins?: AddonModAssignPlugin[]; // Plugins.
 |     plugins?: AddonModAssignPlugin[]; // Plugins.
 | ||||||
|     gradingstatus?: string; // @since 3.2. Grading status.
 |     gradingstatus?: string; // Grading status.
 | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -1539,7 +1450,7 @@ export type AddonModAssignSubmissionAttempt = { | |||||||
|     locked: boolean; // Whether new submissions are locked.
 |     locked: boolean; // Whether new submissions are locked.
 | ||||||
|     graded: boolean; // Whether the submission is graded.
 |     graded: boolean; // Whether the submission is graded.
 | ||||||
|     canedit: boolean; // Whether the user can edit the current submission.
 |     canedit: boolean; // Whether the user can edit the current submission.
 | ||||||
|     caneditowner?: boolean; // @since 3.2. Whether the owner of the submission can edit it.
 |     caneditowner?: boolean; // Whether the owner of the submission can edit it.
 | ||||||
|     cansubmit: boolean; // Whether the user can submit.
 |     cansubmit: boolean; // Whether the user can submit.
 | ||||||
|     extensionduedate: number; // Extension due date.
 |     extensionduedate: number; // Extension due date.
 | ||||||
|     blindmarking: boolean; // Whether blind marking is enabled.
 |     blindmarking: boolean; // Whether blind marking is enabled.
 | ||||||
| @ -1610,7 +1521,7 @@ export type AddonModAssignParticipant = { | |||||||
|     interests?: string; // User interests (separated by commas).
 |     interests?: string; // User interests (separated by commas).
 | ||||||
|     firstaccess?: number; // First access to the site (0 if never).
 |     firstaccess?: number; // First access to the site (0 if never).
 | ||||||
|     lastaccess?: number; // Last access to the site (0 if never).
 |     lastaccess?: number; // Last access to the site (0 if never).
 | ||||||
|     suspended?: boolean; // @since 3.2. Suspend user account, either false to enable user login or true to disable it.
 |     suspended?: boolean; // Suspend user account, either false to enable user login or true to disable it.
 | ||||||
|     description?: string; // User profile description.
 |     description?: string; // User profile description.
 | ||||||
|     descriptionformat?: number; // Int format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 |     descriptionformat?: number; // Int format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||||
|     city?: string; // Home city of the user.
 |     city?: string; // Home city of the user.
 | ||||||
| @ -1647,7 +1558,7 @@ export type AddonModAssignParticipant = { | |||||||
|     }[]; |     }[]; | ||||||
|     submitted: boolean; // Have they submitted their assignment.
 |     submitted: boolean; // Have they submitted their assignment.
 | ||||||
|     requiregrading: boolean; // Is their submission waiting for grading.
 |     requiregrading: boolean; // Is their submission waiting for grading.
 | ||||||
|     grantedextension?: boolean; // @since 3.3. Have they been granted an extension.
 |     grantedextension?: boolean; // Have they been granted an extension.
 | ||||||
|     groupid?: number; // For group assignments this is the group id.
 |     groupid?: number; // For group assignments this is the group id.
 | ||||||
|     groupname?: string; // For group assignments this is the group name.
 |     groupname?: string; // For group assignments this is the group name.
 | ||||||
| }; | }; | ||||||
| @ -1815,45 +1726,6 @@ type AddonModAssignSubmitGradingFormWSParams = { | |||||||
|     jsonformdata: string; // The data from the grading form, encoded as a json array.
 |     jsonformdata: string; // The data from the grading form, encoded as a json array.
 | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /** |  | ||||||
|  * Params of mod_assign_save_grade WS. |  | ||||||
|  */ |  | ||||||
| type AddonModAssignSaveGradeWSParams = { |  | ||||||
|     assignmentid: number; // The assignment id to operate on.
 |  | ||||||
|     userid: number; // The student id to operate on.
 |  | ||||||
|     grade: number; // The new grade for this user. Ignored if advanced grading used.
 |  | ||||||
|     attemptnumber: number; // The attempt number (-1 means latest attempt).
 |  | ||||||
|     addattempt: boolean; // Allow another attempt if the attempt reopen method is manual.
 |  | ||||||
|     workflowstate: string; // The next marking workflow state.
 |  | ||||||
|     applytoall: boolean; // If true, this grade will be applied to all members of the group (for group assignments).
 |  | ||||||
|     plugindata?: AddonModAssignSavePluginData; // Plugin data.
 |  | ||||||
|     advancedgradingdata?: { |  | ||||||
|         guide?: { |  | ||||||
|             criteria: { |  | ||||||
|                 criterionid: number; // Criterion id.
 |  | ||||||
|                 fillings?: { // Filling.
 |  | ||||||
|                     criterionid: number; // Criterion id.
 |  | ||||||
|                     levelid?: number; // Level id.
 |  | ||||||
|                     remark?: string; // Remark.
 |  | ||||||
|                     remarkformat?: number; // Remark format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 |  | ||||||
|                     score: number; // Maximum score.
 |  | ||||||
|                 }[]; |  | ||||||
|             }[]; |  | ||||||
|         }; // Items.
 |  | ||||||
|         rubric?: { |  | ||||||
|             criteria: { |  | ||||||
|                 criterionid: number; // Criterion id.
 |  | ||||||
|                 fillings?: { // Filling.
 |  | ||||||
|                     criterionid: number; // Criterion id.
 |  | ||||||
|                     levelid?: number; // Level id.
 |  | ||||||
|                     remark?: string; // Remark.
 |  | ||||||
|                     remarkformat?: number; // Remark format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 |  | ||||||
|                 }[]; |  | ||||||
|             }[]; |  | ||||||
|         }; // Items.
 |  | ||||||
|     }; // Advanced grading data.
 |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| /** | /** | ||||||
|  * Assignment grade outcomes. |  * Assignment grade outcomes. | ||||||
|  */ |  */ | ||||||
|  | |||||||
| @ -20,7 +20,6 @@ import { makeSingleton } from '@singletons'; | |||||||
| import { CoreCourse, CoreCourseAnyModuleData } from '@features/course/services/course'; | import { CoreCourse, CoreCourseAnyModuleData } from '@features/course/services/course'; | ||||||
| import { CoreCourseModule } from '@features/course/services/course-helper'; | import { CoreCourseModule } from '@features/course/services/course-helper'; | ||||||
| import { CoreNavigationOptions, CoreNavigator } from '@services/navigator'; | import { CoreNavigationOptions, CoreNavigator } from '@services/navigator'; | ||||||
| import { AddonModAssign } from '../assign'; |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Handler to support assign modules. |  * Handler to support assign modules. | ||||||
| @ -54,7 +53,7 @@ export class AddonModAssignModuleHandlerService implements CoreCourseModuleHandl | |||||||
|      * @return Whether or not the handler is enabled on a site level. |      * @return Whether or not the handler is enabled on a site level. | ||||||
|      */ |      */ | ||||||
|     async isEnabled(): Promise<boolean> { |     async isEnabled(): Promise<boolean> { | ||||||
|         return AddonModAssign.isPluginEnabled(); |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -202,15 +202,6 @@ export class AddonModAssignPrefetchHandlerService extends CoreCourseActivityPref | |||||||
|         return CoreCourse.invalidateModule(module.id); |         return CoreCourse.invalidateModule(module.id); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Whether or not the handler is enabled on a site level. |  | ||||||
|      * |  | ||||||
|      * @return A boolean, or a promise resolved with a boolean, indicating if the handler is enabled. |  | ||||||
|      */ |  | ||||||
|     async isEnabled(): Promise<boolean> { |  | ||||||
|         return AddonModAssign.isPluginEnabled(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * @inheritdoc |      * @inheritdoc | ||||||
|      */ |      */ | ||||||
| @ -391,10 +382,7 @@ export class AddonModAssignPrefetchHandlerService extends CoreCourseActivityPref | |||||||
|                     // Participiants already fetched, we don't need to ignore cache now.
 |                     // Participiants already fetched, we don't need to ignore cache now.
 | ||||||
|                     const participants = await AddonModAssignHelper.getParticipants(assign, group.id, { siteId }); |                     const participants = await AddonModAssignHelper.getParticipants(assign, group.id, { siteId }); | ||||||
| 
 | 
 | ||||||
|                     // Fail silently (Moodle < 3.2).
 |                     await CoreUser.prefetchUserAvatars(participants, 'profileimageurl', siteId); | ||||||
|                     await CoreUtils.ignoreErrors( |  | ||||||
|                         CoreUser.prefetchUserAvatars(participants, 'profileimageurl', siteId), |  | ||||||
|                     ); |  | ||||||
| 
 | 
 | ||||||
|                     return; |                     return; | ||||||
|                 })); |                 })); | ||||||
|  | |||||||
| @ -16,7 +16,6 @@ import { AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin } | |||||||
| import { AddonModAssignSubmissionHandler } from '@addons/mod/assign/services/submission-delegate'; | import { AddonModAssignSubmissionHandler } from '@addons/mod/assign/services/submission-delegate'; | ||||||
| import { Injectable, Type } from '@angular/core'; | import { Injectable, Type } from '@angular/core'; | ||||||
| import { CoreComments } from '@features/comments/services/comments'; | import { CoreComments } from '@features/comments/services/comments'; | ||||||
| import { CoreUtils } from '@services/utils/utils'; |  | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
| import { AddonModAssignSubmissionCommentsComponent } from '../component/comments'; | import { AddonModAssignSubmissionCommentsComponent } from '../component/comments'; | ||||||
| 
 | 
 | ||||||
| @ -87,10 +86,7 @@ export class AddonModAssignSubmissionCommentsHandlerService implements AddonModA | |||||||
|         plugin: AddonModAssignPlugin, |         plugin: AddonModAssignPlugin, | ||||||
|         siteId?: string, |         siteId?: string, | ||||||
|     ): Promise<void> { |     ): Promise<void> { | ||||||
| 
 |         await CoreComments.getComments( | ||||||
|         // Fail silently (Moodle < 3.1.1, 3.2)
 |  | ||||||
|         await CoreUtils.ignoreErrors( |  | ||||||
|             CoreComments.getComments( |  | ||||||
|             'module', |             'module', | ||||||
|             assign.cmid, |             assign.cmid, | ||||||
|             'assignsubmission_comments', |             'assignsubmission_comments', | ||||||
| @ -98,7 +94,6 @@ export class AddonModAssignSubmissionCommentsHandlerService implements AddonModA | |||||||
|             'submission_comments', |             'submission_comments', | ||||||
|             0, |             0, | ||||||
|             siteId, |             siteId, | ||||||
|             ), |  | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -24,7 +24,6 @@ import { AddonModAssignSubmissionHandler } from '@addons/mod/assign/services/sub | |||||||
| import { Injectable, Type } from '@angular/core'; | import { Injectable, Type } from '@angular/core'; | ||||||
| import { CoreError } from '@classes/errors/error'; | import { CoreError } from '@classes/errors/error'; | ||||||
| import { CoreFileHelper } from '@services/file-helper'; | import { CoreFileHelper } from '@services/file-helper'; | ||||||
| import { CoreSites } from '@services/sites'; |  | ||||||
| import { CoreTextUtils } from '@services/utils/text'; | import { CoreTextUtils } from '@services/utils/text'; | ||||||
| import { CoreUtils } from '@services/utils/utils'; | import { CoreUtils } from '@services/utils/utils'; | ||||||
| import { CoreWSFile } from '@services/ws'; | import { CoreWSFile } from '@services/ws'; | ||||||
| @ -224,11 +223,7 @@ export class AddonModAssignSubmissionOnlineTextHandlerService implements AddonMo | |||||||
|      * @return Whether or not the handler is enabled for edit on a site level. |      * @return Whether or not the handler is enabled for edit on a site level. | ||||||
|      */ |      */ | ||||||
|     isEnabledForEdit(): boolean { |     isEnabledForEdit(): boolean { | ||||||
|         // There's a bug in Moodle 3.1.0 that doesn't allow submitting HTML, so we'll disable this plugin in that case.
 |         return true; | ||||||
|         // Bug was fixed in 3.1.1 minor release and in 3.2.
 |  | ||||||
|         const currentSite = CoreSites.getCurrentSite(); |  | ||||||
| 
 |  | ||||||
|         return !!currentSite?.isVersionGreaterEqualThan('3.1.1') || !!currentSite?.checkIfAppUsesLocalMobile(); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -441,7 +441,7 @@ export type AddonModBookBookWSData = { | |||||||
|     name: string; // Book name.
 |     name: string; // Book name.
 | ||||||
|     intro: string; // The Book intro.
 |     intro: string; // The Book intro.
 | ||||||
|     introformat: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 |     introformat: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||||
|     introfiles?: CoreWSExternalFile[]; // @since 3.2.
 |     introfiles?: CoreWSExternalFile[]; | ||||||
|     numbering: number; // Book numbering configuration.
 |     numbering: number; // Book numbering configuration.
 | ||||||
|     navstyle: number; // Book navigation style configuration.
 |     navstyle: number; // Book navigation style configuration.
 | ||||||
|     customtitles: number; // Book custom titles type.
 |     customtitles: number; // Book custom titles type.
 | ||||||
|  | |||||||
| @ -46,7 +46,7 @@ | |||||||
|         <ion-button class="ion-margin ion-text-wrap" expand="block" color="primary" (click)="enterChat()"> |         <ion-button class="ion-margin ion-text-wrap" expand="block" color="primary" (click)="enterChat()"> | ||||||
|             {{ 'addon.mod_chat.enterchat' | translate }} |             {{ 'addon.mod_chat.enterchat' | translate }} | ||||||
|         </ion-button> |         </ion-button> | ||||||
|         <ion-button class="ion-margin ion-text-wrap" expand="block" color="light" *ngIf="sessionsAvailable" (click)="viewSessions()"> |         <ion-button class="ion-margin ion-text-wrap" expand="block" color="light" (click)="viewSessions()"> | ||||||
|             {{ 'addon.mod_chat.viewreport' | translate }} |             {{ 'addon.mod_chat.viewreport' | translate }} | ||||||
|         </ion-button> |         </ion-button> | ||||||
|     </ng-container> |     </ng-container> | ||||||
|  | |||||||
| @ -34,7 +34,6 @@ export class AddonModChatIndexComponent extends CoreCourseModuleMainActivityComp | |||||||
|     component = AddonModChatProvider.COMPONENT; |     component = AddonModChatProvider.COMPONENT; | ||||||
|     moduleName = 'chat'; |     moduleName = 'chat'; | ||||||
|     chat?: AddonModChatChat; |     chat?: AddonModChatChat; | ||||||
|     sessionsAvailable = false; |  | ||||||
|     chatInfo?: { |     chatInfo?: { | ||||||
|         date: string; |         date: string; | ||||||
|         fromnow: string; |         fromnow: string; | ||||||
| @ -89,8 +88,6 @@ export class AddonModChatIndexComponent extends CoreCourseModuleMainActivityComp | |||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             this.dataRetrieved.emit(this.chat); |             this.dataRetrieved.emit(this.chat); | ||||||
| 
 |  | ||||||
|             this.sessionsAvailable = await AddonModChat.areSessionsAvailable(); |  | ||||||
|         } finally { |         } finally { | ||||||
|             this.fillContextMenu(refresh); |             this.fillContextMenu(refresh); | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -217,19 +217,6 @@ export class AddonModChatProvider { | |||||||
|         return site.read('mod_chat_get_chat_users', params, preSets); |         return site.read('mod_chat_get_chat_users', params, preSets); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Return whether WS for passed sessions are available. |  | ||||||
|      * |  | ||||||
|      * @param siteId Site ID. If not defined, current site. |  | ||||||
|      * @return Promise resolved with a boolean. |  | ||||||
|      * @since 3.5 |  | ||||||
|      */ |  | ||||||
|     async areSessionsAvailable(siteId?: string): Promise<boolean> { |  | ||||||
|         const site = await CoreSites.getSite(siteId); |  | ||||||
| 
 |  | ||||||
|         return site.wsAvailable('mod_chat_get_sessions') && site.wsAvailable('mod_chat_get_session_messages'); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * Get chat sessions. |      * Get chat sessions. | ||||||
|      * |      * | ||||||
| @ -238,7 +225,6 @@ export class AddonModChatProvider { | |||||||
|      * @param showAll Whether to include incomplete sessions or not. |      * @param showAll Whether to include incomplete sessions or not. | ||||||
|      * @param options Other options. |      * @param options Other options. | ||||||
|      * @return Promise resolved with the list of sessions. |      * @return Promise resolved with the list of sessions. | ||||||
|      * @since 3.5 |  | ||||||
|      */ |      */ | ||||||
|     async getSessions( |     async getSessions( | ||||||
|         chatId: number, |         chatId: number, | ||||||
| @ -275,7 +261,6 @@ export class AddonModChatProvider { | |||||||
|      * @param groupId Group ID, 0 means that the function will determine the user group. |      * @param groupId Group ID, 0 means that the function will determine the user group. | ||||||
|      * @param options Other options. |      * @param options Other options. | ||||||
|      * @return Promise resolved with the list of messages. |      * @return Promise resolved with the list of messages. | ||||||
|      * @since 3.5 |  | ||||||
|      */ |      */ | ||||||
|     async getSessionMessages( |     async getSessionMessages( | ||||||
|         chatId: number, |         chatId: number, | ||||||
| @ -461,7 +446,7 @@ export type AddonModChatChat = { | |||||||
|     name: string; // Chat name.
 |     name: string; // Chat name.
 | ||||||
|     intro: string; // The Chat intro.
 |     intro: string; // The Chat intro.
 | ||||||
|     introformat: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 |     introformat: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||||
|     introfiles?: CoreWSExternalFile[]; // @since 3.2.
 |     introfiles?: CoreWSExternalFile[]; | ||||||
|     chatmethod?: string; // Chat method (sockets, ajax, header_js).
 |     chatmethod?: string; // Chat method (sockets, ajax, header_js).
 | ||||||
|     keepdays?: number; // Keep days.
 |     keepdays?: number; // Keep days.
 | ||||||
|     studentlogs?: number; // Student logs visible to everyone.
 |     studentlogs?: number; // Student logs visible to everyone.
 | ||||||
|  | |||||||
| @ -20,7 +20,6 @@ import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@features/ | |||||||
| import { CoreNavigationOptions, CoreNavigator } from '@services/navigator'; | import { CoreNavigationOptions, CoreNavigator } from '@services/navigator'; | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
| import { AddonModChatIndexComponent } from '../../components/index'; | import { AddonModChatIndexComponent } from '../../components/index'; | ||||||
| import { AddonModChat } from '../chat'; |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Handler to support chat modules. |  * Handler to support chat modules. | ||||||
| @ -59,6 +58,7 @@ export class AddonModChatModuleHandlerService implements CoreCourseModuleHandler | |||||||
|             icon: CoreCourse.getModuleIconSrc(this.modName, 'modicon' in module ? module.modicon : undefined), |             icon: CoreCourse.getModuleIconSrc(this.modName, 'modicon' in module ? module.modicon : undefined), | ||||||
|             title: module.name, |             title: module.name, | ||||||
|             class: 'addon-mod_chat-handler', |             class: 'addon-mod_chat-handler', | ||||||
|  |             showDownloadButton: true, | ||||||
|             action(event: Event, module: CoreCourseModule, courseId: number, options?: CoreNavigationOptions): void { |             action(event: Event, module: CoreCourseModule, courseId: number, options?: CoreNavigationOptions): void { | ||||||
|                 options = options || {}; |                 options = options || {}; | ||||||
|                 options.params = options.params || {}; |                 options.params = options.params || {}; | ||||||
| @ -69,20 +69,9 @@ export class AddonModChatModuleHandlerService implements CoreCourseModuleHandler | |||||||
|             }, |             }, | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         this.checkDownloadButton(data); |  | ||||||
| 
 |  | ||||||
|         return data; |         return data; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Check whether download button should be displayed. |  | ||||||
|      * |  | ||||||
|      * @param data Handler data. |  | ||||||
|      */ |  | ||||||
|     protected async checkDownloadButton(data: CoreCourseModuleHandlerData): Promise<void> { |  | ||||||
|         data.showDownloadButton = await AddonModChat.areSessionsAvailable(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * @inheritdoc |      * @inheritdoc | ||||||
|      */ |      */ | ||||||
|  | |||||||
| @ -32,13 +32,6 @@ export class AddonModChatPrefetchHandlerService extends CoreCourseActivityPrefet | |||||||
|     modName = 'chat'; |     modName = 'chat'; | ||||||
|     component = AddonModChatProvider.COMPONENT; |     component = AddonModChatProvider.COMPONENT; | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * @inheritdoc |  | ||||||
|      */ |  | ||||||
|     async isEnabled(): Promise<boolean> { |  | ||||||
|         return AddonModChat.areSessionsAvailable(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * @inheritdoc |      * @inheritdoc | ||||||
|      */ |      */ | ||||||
|  | |||||||
| @ -484,7 +484,7 @@ export type AddonModChoiceChoice = { | |||||||
|     name: string; // Choice name.
 |     name: string; // Choice name.
 | ||||||
|     intro: string; // The choice intro.
 |     intro: string; // The choice intro.
 | ||||||
|     introformat: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 |     introformat: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||||
|     introfiles?: CoreWSExternalFile[]; // @since 3.2.
 |     introfiles?: CoreWSExternalFile[]; | ||||||
|     publish?: boolean; // If choice is published.
 |     publish?: boolean; // If choice is published.
 | ||||||
|     showresults?: number; // 0 never, 1 after answer, 2 after close, 3 always.
 |     showresults?: number; // 0 never, 1 after answer, 2 after close, 3 always.
 | ||||||
|     display?: number; // Display mode (vertical, horizontal).
 |     display?: number; // Display mode (vertical, horizontal).
 | ||||||
|  | |||||||
| @ -950,19 +950,6 @@ export class AddonModDataProvider { | |||||||
|         await site.invalidateWsCacheForKey(this.getEntryCacheKey(dataId, entryId)); |         await site.invalidateWsCacheForKey(this.getEntryCacheKey(dataId, entryId)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Return whether or not the plugin is enabled in a certain site. Plugin is enabled if the database WS are available. |  | ||||||
|      * |  | ||||||
|      * @param siteId Site ID. If not defined, current site. |  | ||||||
|      * @return Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise. |  | ||||||
|      * @since 3.3 |  | ||||||
|      */ |  | ||||||
|     async isPluginEnabled(siteId?: string): Promise<boolean> { |  | ||||||
|         const site = await CoreSites.getSite(siteId); |  | ||||||
| 
 |  | ||||||
|         return site.wsAvailable('mod_data_get_data_access_information'); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * Report the database as being viewed. |      * Report the database as being viewed. | ||||||
|      * |      * | ||||||
|  | |||||||
| @ -17,7 +17,6 @@ import { Params } from '@angular/router'; | |||||||
| import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; | import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; | ||||||
| import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; | import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
| import { AddonModData } from '../data'; |  | ||||||
| import { AddonModDataHelper } from '../data-helper'; | import { AddonModDataHelper } from '../data-helper'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -56,7 +55,7 @@ export class AddonModDataApproveLinkHandlerService extends CoreContentLinksHandl | |||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return AddonModData.isPluginEnabled(siteId); |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -17,7 +17,6 @@ import { Params } from '@angular/router'; | |||||||
| import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; | import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; | ||||||
| import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; | import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
| import { AddonModData } from '../data'; |  | ||||||
| import { AddonModDataHelper } from '../data-helper'; | import { AddonModDataHelper } from '../data-helper'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -54,7 +53,7 @@ export class AddonModDataDeleteLinkHandlerService extends CoreContentLinksHandle | |||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return AddonModData.isPluginEnabled(siteId); |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -20,7 +20,6 @@ import { CoreCourse } from '@features/course/services/course'; | |||||||
| import { CoreNavigator } from '@services/navigator'; | import { CoreNavigator } from '@services/navigator'; | ||||||
| import { CoreDomUtils } from '@services/utils/dom'; | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
| import { AddonModData } from '../data'; |  | ||||||
| import { AddonModDataModuleHandlerService } from './module'; | import { AddonModDataModuleHandlerService } from './module'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -72,7 +71,7 @@ export class AddonModDataEditLinkHandlerService extends CoreContentLinksHandlerB | |||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return AddonModData.isPluginEnabled(siteId); |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -15,7 +15,6 @@ | |||||||
| import { Injectable } from '@angular/core'; | import { Injectable } from '@angular/core'; | ||||||
| import { CoreContentLinksModuleIndexHandler } from '@features/contentlinks/classes/module-index-handler'; | import { CoreContentLinksModuleIndexHandler } from '@features/contentlinks/classes/module-index-handler'; | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
| import { AddonModData } from '../data'; |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Handler to treat links to data. |  * Handler to treat links to data. | ||||||
| @ -29,12 +28,5 @@ export class AddonModDataIndexLinkHandlerService extends CoreContentLinksModuleI | |||||||
|         super('AddonModData', 'data', 'd'); |         super('AddonModData', 'data', 'd'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * @inheritdoc |  | ||||||
|      */ |  | ||||||
|     isEnabled(siteId: string): Promise<boolean> { |  | ||||||
|         return AddonModData.isPluginEnabled(siteId); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } | } | ||||||
| export const AddonModDataIndexLinkHandler = makeSingleton(AddonModDataIndexLinkHandlerService); | export const AddonModDataIndexLinkHandler = makeSingleton(AddonModDataIndexLinkHandlerService); | ||||||
|  | |||||||
| @ -15,7 +15,6 @@ | |||||||
| import { Injectable } from '@angular/core'; | import { Injectable } from '@angular/core'; | ||||||
| import { CoreContentLinksModuleListHandler } from '@features/contentlinks/classes/module-list-handler'; | import { CoreContentLinksModuleListHandler } from '@features/contentlinks/classes/module-list-handler'; | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
| import { AddonModData } from '../data'; |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Handler to treat links to data list page. |  * Handler to treat links to data list page. | ||||||
| @ -29,12 +28,5 @@ export class AddonModDataListLinkHandlerService extends CoreContentLinksModuleLi | |||||||
|         super('AddonModData', 'data'); |         super('AddonModData', 'data'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * @inheritdoc |  | ||||||
|      */ |  | ||||||
|     isEnabled(siteId?: string): Promise<boolean> { |  | ||||||
|         return AddonModData.isPluginEnabled(siteId); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } | } | ||||||
| export const AddonModDataListLinkHandler = makeSingleton(AddonModDataListLinkHandlerService); | export const AddonModDataListLinkHandler = makeSingleton(AddonModDataListLinkHandlerService); | ||||||
|  | |||||||
| @ -20,7 +20,6 @@ import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@features/ | |||||||
| import { CoreNavigationOptions, CoreNavigator } from '@services/navigator'; | import { CoreNavigationOptions, CoreNavigator } from '@services/navigator'; | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
| import { AddonModDataIndexComponent } from '../../components/index'; | import { AddonModDataIndexComponent } from '../../components/index'; | ||||||
| import { AddonModData } from '../data'; |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Handler to support data modules. |  * Handler to support data modules. | ||||||
| @ -50,8 +49,8 @@ export class AddonModDataModuleHandlerService implements CoreCourseModuleHandler | |||||||
|     /** |     /** | ||||||
|      * @inheritdoc |      * @inheritdoc | ||||||
|      */ |      */ | ||||||
|     isEnabled(): Promise<boolean> { |     async isEnabled(): Promise<boolean> { | ||||||
|         return AddonModData.isPluginEnabled(); |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -203,13 +203,6 @@ export class AddonModDataPrefetchHandlerService extends CoreCourseActivityPrefet | |||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * @inheritdoc |  | ||||||
|      */ |  | ||||||
|     async isEnabled(): Promise<boolean> { |  | ||||||
|         return AddonModData.isPluginEnabled(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * @inheritdoc |      * @inheritdoc | ||||||
|      */ |      */ | ||||||
|  | |||||||
| @ -20,7 +20,6 @@ import { CoreCourse } from '@features/course/services/course'; | |||||||
| import { CoreNavigator } from '@services/navigator'; | import { CoreNavigator } from '@services/navigator'; | ||||||
| import { CoreDomUtils } from '@services/utils/dom'; | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
| import { AddonModData } from '../data'; |  | ||||||
| import { AddonModDataModuleHandlerService } from './module'; | import { AddonModDataModuleHandlerService } from './module'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -87,7 +86,7 @@ export class AddonModDataShowLinkHandlerService extends CoreContentLinksHandlerB | |||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return AddonModData.isPluginEnabled(siteId); |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -17,7 +17,6 @@ import { CoreTagFeedComponent } from '@features/tag/components/feed/feed'; | |||||||
| import { CoreTagAreaHandler } from '@features/tag/services/tag-area-delegate'; | import { CoreTagAreaHandler } from '@features/tag/services/tag-area-delegate'; | ||||||
| import { CoreTagFeedElement, CoreTagHelper } from '@features/tag/services/tag-helper'; | import { CoreTagFeedElement, CoreTagHelper } from '@features/tag/services/tag-helper'; | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
| import { AddonModData } from '../data'; |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Handler to support tags. |  * Handler to support tags. | ||||||
| @ -32,7 +31,7 @@ export class AddonModDataTagAreaHandlerService implements CoreTagAreaHandler { | |||||||
|      * @inheritdoc |      * @inheritdoc | ||||||
|      */ |      */ | ||||||
|     async isEnabled(): Promise<boolean> { |     async isEnabled(): Promise<boolean> { | ||||||
|         return AddonModData.isPluginEnabled(); |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -1089,20 +1089,6 @@ export class AddonModFeedbackProvider { | |||||||
|         return CoreUtils.promiseWorks(site.read('mod_feedback_get_last_completed', params, preSets)); |         return CoreUtils.promiseWorks(site.read('mod_feedback_get_last_completed', params, preSets)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Return whether or not the plugin is enabled in a certain site. Plugin is enabled if the feedback WS are available. |  | ||||||
|      * |  | ||||||
|      * @param siteId Site ID. If not defined, current site. |  | ||||||
|      * @return Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise. |  | ||||||
|      * @since 3.3 |  | ||||||
|      */ |  | ||||||
|     async isPluginEnabled(siteId?: string): Promise<boolean> { |  | ||||||
|         const site = await CoreSites.getSite(siteId); |  | ||||||
| 
 |  | ||||||
|         return site.wsAvailable('mod_feedback_get_feedbacks_by_courses') && |  | ||||||
|                 site.wsAvailable('mod_feedback_get_feedback_access_information'); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * Report the feedback as being viewed. |      * Report the feedback as being viewed. | ||||||
|      * |      * | ||||||
|  | |||||||
| @ -19,7 +19,6 @@ import { CoreCourse } from '@features/course/services/course'; | |||||||
| import { CoreNavigator } from '@services/navigator'; | import { CoreNavigator } from '@services/navigator'; | ||||||
| import { CoreDomUtils } from '@services/utils/dom'; | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
| import { AddonModFeedback } from '../feedback'; |  | ||||||
| import { AddonModFeedbackModuleHandlerService } from './module'; | import { AddonModFeedbackModuleHandlerService } from './module'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -84,7 +83,7 @@ export class AddonModFeedbackAnalysisLinkHandlerService extends CoreContentLinks | |||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return AddonModFeedback.isPluginEnabled(siteId); |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -19,7 +19,6 @@ import { CoreCourse } from '@features/course/services/course'; | |||||||
| import { CoreNavigator } from '@services/navigator'; | import { CoreNavigator } from '@services/navigator'; | ||||||
| import { CoreDomUtils } from '@services/utils/dom'; | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
| import { AddonModFeedback } from '../feedback'; |  | ||||||
| import { AddonModFeedbackModuleHandlerService } from './module'; | import { AddonModFeedbackModuleHandlerService } from './module'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -72,7 +71,7 @@ export class AddonModFeedbackCompleteLinkHandlerService extends CoreContentLinks | |||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return AddonModFeedback.isPluginEnabled(siteId); |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -15,7 +15,6 @@ | |||||||
| import { Injectable } from '@angular/core'; | import { Injectable } from '@angular/core'; | ||||||
| import { CoreContentLinksModuleIndexHandler } from '@features/contentlinks/classes/module-index-handler'; | import { CoreContentLinksModuleIndexHandler } from '@features/contentlinks/classes/module-index-handler'; | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
| import { AddonModFeedback } from '../feedback'; |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Handler to treat links to feedback. |  * Handler to treat links to feedback. | ||||||
| @ -29,19 +28,6 @@ export class AddonModFeedbackIndexLinkHandlerService extends CoreContentLinksMod | |||||||
|         super('AddonModFeedback', 'feedback'); |         super('AddonModFeedback', 'feedback'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Check if the handler is enabled for a certain site (site + user) and a URL. |  | ||||||
|      * |  | ||||||
|      * @param siteId The site ID. |  | ||||||
|      * @param url The URL to treat. |  | ||||||
|      * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} |  | ||||||
|      * @param courseId Course ID related to the URL. Optional but recommended. |  | ||||||
|      * @return Whether the handler is enabled for the URL and site. |  | ||||||
|      */ |  | ||||||
|     isEnabled(): Promise<boolean> { |  | ||||||
|         return AddonModFeedback.isPluginEnabled(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const AddonModFeedbackIndexLinkHandler = makeSingleton(AddonModFeedbackIndexLinkHandlerService); | export const AddonModFeedbackIndexLinkHandler = makeSingleton(AddonModFeedbackIndexLinkHandlerService); | ||||||
|  | |||||||
| @ -15,7 +15,6 @@ | |||||||
| import { Injectable } from '@angular/core'; | import { Injectable } from '@angular/core'; | ||||||
| import { CoreContentLinksModuleListHandler } from '@features/contentlinks/classes/module-list-handler'; | import { CoreContentLinksModuleListHandler } from '@features/contentlinks/classes/module-list-handler'; | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
| import { AddonModFeedback } from '../feedback'; |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Handler to treat links to feedback list page. |  * Handler to treat links to feedback list page. | ||||||
| @ -29,13 +28,6 @@ export class AddonModFeedbackListLinkHandlerService extends CoreContentLinksModu | |||||||
|         super('AddonModFeedback', 'feedback'); |         super('AddonModFeedback', 'feedback'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * @inheritdoc |  | ||||||
|      */ |  | ||||||
|     isEnabled(): Promise<boolean> { |  | ||||||
|         return AddonModFeedback.isPluginEnabled(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const AddonModFeedbackListLinkHandler = makeSingleton(AddonModFeedbackListLinkHandlerService); | export const AddonModFeedbackListLinkHandler = makeSingleton(AddonModFeedbackListLinkHandlerService); | ||||||
|  | |||||||
| @ -19,7 +19,6 @@ import { CoreCourseModule } from '@features/course/services/course-helper'; | |||||||
| import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@features/course/services/module-delegate'; | import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@features/course/services/module-delegate'; | ||||||
| import { CoreNavigationOptions, CoreNavigator } from '@services/navigator'; | import { CoreNavigationOptions, CoreNavigator } from '@services/navigator'; | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
| import { AddonModFeedback } from '../feedback'; |  | ||||||
| import { AddonModFeedbackIndexComponent } from '../../components/index'; | import { AddonModFeedbackIndexComponent } from '../../components/index'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -48,8 +47,8 @@ export class AddonModFeedbackModuleHandlerService implements CoreCourseModuleHan | |||||||
|     /** |     /** | ||||||
|      * @inheritdoc |      * @inheritdoc | ||||||
|      */ |      */ | ||||||
|     isEnabled(): Promise<boolean> { |     async isEnabled(): Promise<boolean> { | ||||||
|         return AddonModFeedback.isPluginEnabled(); |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -110,13 +110,6 @@ export class AddonModFeedbackPrefetchHandlerService extends CoreCourseActivityPr | |||||||
|         return accessData.isopen; |         return accessData.isopen; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * @inheritdoc |  | ||||||
|      */ |  | ||||||
|     isEnabled(): Promise<boolean> { |  | ||||||
|         return AddonModFeedback.isPluginEnabled(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * @inheritdoc |      * @inheritdoc | ||||||
|      */ |      */ | ||||||
|  | |||||||
| @ -19,7 +19,6 @@ import { CoreCourse } from '@features/course/services/course'; | |||||||
| import { CoreNavigator } from '@services/navigator'; | import { CoreNavigator } from '@services/navigator'; | ||||||
| import { CoreDomUtils } from '@services/utils/dom'; | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
| import { AddonModFeedback } from '../feedback'; |  | ||||||
| import { AddonModFeedbackModuleHandlerService } from './module'; | import { AddonModFeedbackModuleHandlerService } from './module'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -72,7 +71,7 @@ export class AddonModFeedbackPrintLinkHandlerService extends CoreContentLinksHan | |||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return AddonModFeedback.isPluginEnabled(siteId); |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -19,7 +19,6 @@ import { CorePushNotificationsNotificationBasicData } from '@features/pushnotifi | |||||||
| 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 } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
| import { AddonModFeedback } from '../feedback'; |  | ||||||
| import { AddonModFeedbackHelper } from '../feedback-helper'; | import { AddonModFeedbackHelper } from '../feedback-helper'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -39,7 +38,7 @@ export class AddonModFeedbackPushClickHandlerService implements CorePushNotifica | |||||||
|         if (CoreUtils.isTrueOrOne(notification.notif) && notification.moodlecomponent == 'mod_feedback' && |         if (CoreUtils.isTrueOrOne(notification.notif) && notification.moodlecomponent == 'mod_feedback' && | ||||||
|                 (notification.name == 'submission' || notification.name == 'message')) { |                 (notification.name == 'submission' || notification.name == 'message')) { | ||||||
| 
 | 
 | ||||||
|             return AddonModFeedback.isPluginEnabled(notification.site); |             return true; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return false; |         return false; | ||||||
|  | |||||||
| @ -16,7 +16,6 @@ import { Injectable } from '@angular/core'; | |||||||
| import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; | import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; | ||||||
| import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; | import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
| import { AddonModFeedback } from '../feedback'; |  | ||||||
| import { AddonModFeedbackHelper } from '../feedback-helper'; | import { AddonModFeedbackHelper } from '../feedback-helper'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -50,7 +49,7 @@ export class AddonModFeedbackShowEntriesLinkHandlerService extends CoreContentLi | |||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return AddonModFeedback.isPluginEnabled(siteId); |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -19,7 +19,6 @@ import { CoreCourse } from '@features/course/services/course'; | |||||||
| import { CoreNavigator } from '@services/navigator'; | import { CoreNavigator } from '@services/navigator'; | ||||||
| import { CoreDomUtils } from '@services/utils/dom'; | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
| import { AddonModFeedback } from '../feedback'; |  | ||||||
| import { AddonModFeedbackModuleHandlerService } from './module'; | import { AddonModFeedbackModuleHandlerService } from './module'; | ||||||
| /** | /** | ||||||
|  * Content links handler for feedback show non respondents. |  * Content links handler for feedback show non respondents. | ||||||
| @ -67,7 +66,7 @@ export class AddonModFeedbackShowNonRespondentsLinkHandlerService extends CoreCo | |||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return AddonModFeedback.isPluginEnabled(siteId); |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -18,7 +18,6 @@ import { Params } from '@angular/router'; | |||||||
| import { CoreCourseModuleMainResourceComponent } from '@features/course/classes/main-resource-component'; | import { CoreCourseModuleMainResourceComponent } from '@features/course/classes/main-resource-component'; | ||||||
| import { CoreCourseContentsPage } from '@features/course/pages/contents/contents'; | import { CoreCourseContentsPage } from '@features/course/pages/contents/contents'; | ||||||
| import { CoreCourse } from '@features/course/services/course'; | import { CoreCourse } from '@features/course/services/course'; | ||||||
| import { CoreApp } from '@services/app'; |  | ||||||
| import { CoreNavigator } from '@services/navigator'; | import { CoreNavigator } from '@services/navigator'; | ||||||
| import { Md5 } from 'ts-md5'; | import { Md5 } from 'ts-md5'; | ||||||
| import { AddonModFolder, AddonModFolderFolder, AddonModFolderProvider } from '../../services/folder'; | import { AddonModFolder, AddonModFolderFolder, AddonModFolderProvider } from '../../services/folder'; | ||||||
| @ -41,7 +40,6 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo | |||||||
|     @Input() subfolder?: AddonModFolderFolderFormattedData; // Subfolder to show.
 |     @Input() subfolder?: AddonModFolderFolderFormattedData; // Subfolder to show.
 | ||||||
| 
 | 
 | ||||||
|     component = AddonModFolderProvider.COMPONENT; |     component = AddonModFolderProvider.COMPONENT; | ||||||
|     canGetFolder = false; |  | ||||||
|     contents?: AddonModFolderFolderFormattedData; |     contents?: AddonModFolderFolderFormattedData; | ||||||
| 
 | 
 | ||||||
|     constructor(@Optional() courseContentsPage?: CoreCourseContentsPage) { |     constructor(@Optional() courseContentsPage?: CoreCourseContentsPage) { | ||||||
| @ -54,8 +52,6 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo | |||||||
|     async ngOnInit(): Promise<void> { |     async ngOnInit(): Promise<void> { | ||||||
|         super.ngOnInit(); |         super.ngOnInit(); | ||||||
| 
 | 
 | ||||||
|         this.canGetFolder = AddonModFolder.isGetFolderWSAvailable(); |  | ||||||
| 
 |  | ||||||
|         if (this.subfolder) { |         if (this.subfolder) { | ||||||
|             this.description = this.folderInstance ? this.folderInstance.intro : this.module.description; |             this.description = this.folderInstance ? this.folderInstance.intro : this.module.description; | ||||||
|             this.contents = this.subfolder; |             this.contents = this.subfolder; | ||||||
| @ -98,18 +94,8 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo | |||||||
|      */ |      */ | ||||||
|     protected async fetchContent(refresh = false): Promise<void> { |     protected async fetchContent(refresh = false): Promise<void> { | ||||||
|         try { |         try { | ||||||
|             if (this.canGetFolder) { |  | ||||||
|             this.folderInstance = await AddonModFolder.getFolder(this.courseId, this.module.id); |             this.folderInstance = await AddonModFolder.getFolder(this.courseId, this.module.id); | ||||||
|             await CoreCourse.loadModuleContents(this.module, this.courseId, undefined, false, refresh); |             await CoreCourse.loadModuleContents(this.module, this.courseId, undefined, false, refresh); | ||||||
|             } else { |  | ||||||
|                 const module = await CoreCourse.getModule(this.module.id, this.courseId); |  | ||||||
| 
 |  | ||||||
|                 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; |  | ||||||
|                 } |  | ||||||
|                 this.module = module; |  | ||||||
|             } |  | ||||||
| 
 | 
 | ||||||
|             this.dataRetrieved.emit(this.folderInstance || this.module); |             this.dataRetrieved.emit(this.folderInstance || this.module); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -122,16 +122,6 @@ export class AddonModFolderProvider { | |||||||
|         await site.invalidateWsCacheForKey(this.getFolderCacheKey(courseId)); |         await site.invalidateWsCacheForKey(this.getFolderCacheKey(courseId)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Returns whether or not getFolder WS available or not. |  | ||||||
|      * |  | ||||||
|      * @return If WS is available. |  | ||||||
|      * @since 3.3 |  | ||||||
|      */ |  | ||||||
|     isGetFolderWSAvailable(): boolean { |  | ||||||
|         return CoreSites.wsAvailableInCurrentSite('mod_folder_get_folders_by_courses'); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * Report a folder as being viewed. |      * Report a folder as being viewed. | ||||||
|      * |      * | ||||||
|  | |||||||
| @ -35,10 +35,7 @@ export class AddonModFolderPrefetchHandlerService extends CoreCourseResourcePref | |||||||
|         const promises: Promise<unknown>[] = []; |         const promises: Promise<unknown>[] = []; | ||||||
| 
 | 
 | ||||||
|         promises.push(super.downloadOrPrefetch(module, courseId, prefetch)); |         promises.push(super.downloadOrPrefetch(module, courseId, prefetch)); | ||||||
| 
 |  | ||||||
|         if (AddonModFolder.isGetFolderWSAvailable()) { |  | ||||||
|         promises.push(AddonModFolder.getFolder(courseId, module.id)); |         promises.push(AddonModFolder.getFolder(courseId, module.id)); | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         await Promise.all(promises); |         await Promise.all(promises); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -359,12 +359,7 @@ export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy, CanLea | |||||||
|      * @return Promise resolved with the list of groups. |      * @return Promise resolved with the list of groups. | ||||||
|      */ |      */ | ||||||
|     protected addAllParticipantsOption(groups: CoreGroup[], check: boolean): Promise<CoreGroup[]> { |     protected addAllParticipantsOption(groups: CoreGroup[], check: boolean): Promise<CoreGroup[]> { | ||||||
|         if (!AddonModForum.isAllParticipantsFixed()) { |         let promise: Promise<boolean>; | ||||||
|             // All participants has a bug, don't add it.
 |  | ||||||
|             return Promise.resolve(groups); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         let promise; |  | ||||||
| 
 | 
 | ||||||
|         if (check) { |         if (check) { | ||||||
|             // We need to check if the user can add a discussion to all participants.
 |             // We need to check if the user can add a discussion to all participants.
 | ||||||
|  | |||||||
| @ -308,15 +308,6 @@ export class AddonModForumProvider { | |||||||
|         return index >= 0 ? posts.splice(index, 1).pop() : undefined; |         return index >= 0 ? posts.splice(index, 1).pop() : undefined; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * There was a bug adding new discussions to All Participants (see MDL-57962). Check if it's fixed. |  | ||||||
|      * |  | ||||||
|      * @return True if fixed, false otherwise. |  | ||||||
|      */ |  | ||||||
|     isAllParticipantsFixed(): boolean { |  | ||||||
|         return !!CoreSites.getCurrentSite()?.isVersionGreaterEqualThan(['3.1.5', '3.2.2']); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * Returns whether or not getDiscussionPost WS available or not. |      * Returns whether or not getDiscussionPost WS available or not. | ||||||
|      * |      * | ||||||
|  | |||||||
| @ -162,7 +162,7 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity | |||||||
|             this.glossary = await AddonModGlossary.getGlossary(this.courseId, this.module.id); |             this.glossary = await AddonModGlossary.getGlossary(this.courseId, this.module.id); | ||||||
| 
 | 
 | ||||||
|             this.description = this.glossary.intro || this.description; |             this.description = this.glossary.intro || this.description; | ||||||
|             this.canAdd = (AddonModGlossary.isPluginEnabledForEditing() && !!this.glossary.canaddentry) || false; |             this.canAdd = !!this.glossary.canaddentry || false; | ||||||
| 
 | 
 | ||||||
|             this.dataRetrieved.emit(this.glossary); |             this.dataRetrieved.emit(this.glossary); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -22,7 +22,6 @@ import { CoreRatingInfo } from '@features/rating/services/rating'; | |||||||
| import { CoreTagItem } from '@features/tag/services/tag'; | import { CoreTagItem } from '@features/tag/services/tag'; | ||||||
| import { CoreApp } from '@services/app'; | import { CoreApp } from '@services/app'; | ||||||
| import { CoreSites, CoreSitesCommonWSOptions, CoreSitesReadingStrategy } from '@services/sites'; | import { CoreSites, CoreSitesCommonWSOptions, CoreSitesReadingStrategy } from '@services/sites'; | ||||||
| import { CoreTextUtils } from '@services/utils/text'; |  | ||||||
| import { CoreUtils } from '@services/utils/utils'; | import { CoreUtils } from '@services/utils/utils'; | ||||||
| import { CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws'; | import { CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws'; | ||||||
| import { makeSingleton, Translate } from '@singletons'; | import { makeSingleton, Translate } from '@singletons'; | ||||||
| @ -964,11 +963,6 @@ export class AddonModGlossaryProvider { | |||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Workaround for bug MDL-57737.
 |  | ||||||
|         if (!site.isVersionGreaterEqualThan('3.2.2')) { |  | ||||||
|             params.definition = CoreTextUtils.cleanTags(params.definition); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         const response = await site.write<AddonModGlossaryAddEntryWSResponse>('mod_glossary_add_entry', params); |         const response = await site.write<AddonModGlossaryAddEntryWSResponse>('mod_glossary_add_entry', params); | ||||||
| 
 | 
 | ||||||
|         return response.entryid; |         return response.entryid; | ||||||
| @ -1007,16 +1001,6 @@ export class AddonModGlossaryProvider { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Return whether or not the plugin is enabled for editing in the current site. Plugin is enabled if the glossary WS are |  | ||||||
|      * available. |  | ||||||
|      * |  | ||||||
|      * @return Whether the glossary editing is available or not. |  | ||||||
|      */ |  | ||||||
|     isPluginEnabledForEditing(): boolean { |  | ||||||
|         return !!CoreSites.getCurrentSite()?.wsAvailable('mod_glossary_add_entry'); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * Report a glossary as being viewed. |      * Report a glossary as being viewed. | ||||||
|      * |      * | ||||||
|  | |||||||
| @ -19,7 +19,7 @@ import { CoreCourse, CoreCourseAnyModuleData } from '@features/course/services/c | |||||||
| import { CoreCourses } from '@features/courses/services/courses'; | import { CoreCourses } from '@features/courses/services/courses'; | ||||||
| import { CoreUser } from '@features/user/services/user'; | import { CoreUser } from '@features/user/services/user'; | ||||||
| import { CoreFilepool } from '@services/filepool'; | import { CoreFilepool } from '@services/filepool'; | ||||||
| import { CoreSites, CoreSitesReadingStrategy } from '@services/sites'; | import { CoreSitesReadingStrategy } from '@services/sites'; | ||||||
| import { CoreUtils } from '@services/utils/utils'; | import { CoreUtils } from '@services/utils/utils'; | ||||||
| import { CoreWSFile } from '@services/ws'; | import { CoreWSFile } from '@services/ws'; | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
| @ -73,16 +73,12 @@ export class AddonModGlossaryPrefetchHandlerService extends CoreCourseActivityPr | |||||||
|     ): CoreWSFile[] { |     ): CoreWSFile[] { | ||||||
|         let files = this.getIntroFilesFromInstance(module, glossary); |         let files = this.getIntroFilesFromInstance(module, glossary); | ||||||
| 
 | 
 | ||||||
|         const getInlineFiles = CoreSites.getCurrentSite()?.isVersionGreaterEqualThan('3.2'); |  | ||||||
| 
 |  | ||||||
|         // Get entries files.
 |         // Get entries files.
 | ||||||
|         entries.forEach((entry) => { |         entries.forEach((entry) => { | ||||||
|             files = files.concat(entry.attachments || []); |             files = files.concat(entry.attachments || []); | ||||||
| 
 | 
 | ||||||
|             if (getInlineFiles && entry.definitioninlinefiles && entry.definitioninlinefiles.length) { |             if (entry.definitioninlinefiles && entry.definitioninlinefiles.length) { | ||||||
|                 files = files.concat(entry.definitioninlinefiles); |                 files = files.concat(entry.definitioninlinefiles); | ||||||
|             } else if (entry.definition && !getInlineFiles) { |  | ||||||
|                 files = files.concat(CoreFilepool.extractDownloadableFilesFromHtmlAsFakeFileObjects(entry.definition)); |  | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -744,6 +744,7 @@ export class AddonModH5PActivityProvider { | |||||||
|      * Delete launcher. |      * Delete launcher. | ||||||
|      * |      * | ||||||
|      * @return Promise resolved when the launcher file is deleted. |      * @return Promise resolved when the launcher file is deleted. | ||||||
|  |      * @since 3.9 | ||||||
|      */ |      */ | ||||||
|     async isPluginEnabled(siteId?: string): Promise<boolean> { |     async isPluginEnabled(siteId?: string): Promise<boolean> { | ||||||
|         const site = await CoreSites.getSite(siteId); |         const site = await CoreSites.getSite(siteId); | ||||||
| @ -788,7 +789,7 @@ export class AddonModH5PActivityProvider { | |||||||
|         const site = await CoreSites.getSite(options.siteId); |         const site = await CoreSites.getSite(options.siteId); | ||||||
| 
 | 
 | ||||||
|         if (!site.wsAvailable('mod_h5pactivity_log_report_viewed')) { |         if (!site.wsAvailable('mod_h5pactivity_log_report_viewed')) { | ||||||
|             // Site doesn't support the WS, stop.
 |             // Site doesn't support the WS, stop. Added in Moodle 3.11.
 | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -19,7 +19,7 @@ import { CoreSitesReadingStrategy } from '@services/sites'; | |||||||
| import { CoreUtils } from '@services/utils/utils'; | import { CoreUtils } from '@services/utils/utils'; | ||||||
| import { CoreWSFile } from '@services/ws'; | import { CoreWSFile } from '@services/ws'; | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
| import { AddonModLabel, AddonModLabelLabel, AddonModLabelProvider } from '../label'; | import { AddonModLabel, AddonModLabelProvider } from '../label'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Handler to prefetch labels. |  * Handler to prefetch labels. | ||||||
| @ -37,13 +37,9 @@ export class AddonModLabelPrefetchHandlerService extends CoreCourseResourcePrefe | |||||||
|      * @inheritdoc |      * @inheritdoc | ||||||
|      */ |      */ | ||||||
|     async getIntroFiles(module: CoreCourseAnyModuleData, courseId: number, ignoreCache?: boolean): Promise<CoreWSFile[]> { |     async getIntroFiles(module: CoreCourseAnyModuleData, courseId: number, ignoreCache?: boolean): Promise<CoreWSFile[]> { | ||||||
|         let label: AddonModLabelLabel | undefined; |         const label = await AddonModLabel.getLabel(courseId, module.id, { | ||||||
| 
 |  | ||||||
|         if (AddonModLabel.isGetLabelAvailableForSite()) { |  | ||||||
|             label = await AddonModLabel.getLabel(courseId, module.id, { |  | ||||||
|             readingStrategy: ignoreCache ? CoreSitesReadingStrategy.ONLY_NETWORK : undefined, |             readingStrategy: ignoreCache ? CoreSitesReadingStrategy.ONLY_NETWORK : undefined, | ||||||
|         }); |         }); | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         return this.getIntroFilesFromInstance(module, label); |         return this.getIntroFilesFromInstance(module, label); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -136,32 +136,6 @@ export class AddonModLabelProvider { | |||||||
|         await CoreUtils.allPromises(promises); |         await CoreUtils.allPromises(promises); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Check if the site has the WS to get label data. |  | ||||||
|      * |  | ||||||
|      * @param siteId Site ID. If not defined, current site. |  | ||||||
|      * @return Promise resolved with boolean: whether it's available. |  | ||||||
|      * @since 3.3 |  | ||||||
|      */ |  | ||||||
|     async isGetLabelAvailable(siteId?: string): Promise<boolean> { |  | ||||||
|         const site = await CoreSites.getSite(siteId); |  | ||||||
| 
 |  | ||||||
|         return site.wsAvailable('mod_label_get_labels_by_courses'); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Check if the site has the WS to get label data. |  | ||||||
|      * |  | ||||||
|      * @param site Site. If not defined, current site. |  | ||||||
|      * @return Whether it's available. |  | ||||||
|      * @since 3.3 |  | ||||||
|      */ |  | ||||||
|     isGetLabelAvailableForSite(site?: CoreSite): boolean { |  | ||||||
|         site = site || CoreSites.getCurrentSite(); |  | ||||||
| 
 |  | ||||||
|         return !!site?.wsAvailable('mod_label_get_labels_by_courses'); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } | } | ||||||
| export const AddonModLabel = makeSingleton(AddonModLabelProvider); | export const AddonModLabel = makeSingleton(AddonModLabelProvider); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -82,21 +82,6 @@ export class AddonModLessonGradeLinkHandlerService extends CoreContentLinksModul | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Check if the handler is enabled for a certain site (site + user) and a URL. |  | ||||||
|      * If not defined, defaults to true. |  | ||||||
|      * |  | ||||||
|      * @param siteId The site ID. |  | ||||||
|      * @param url The URL to treat. |  | ||||||
|      * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} |  | ||||||
|      * @param courseId Course ID related to the URL. Optional but recommended. |  | ||||||
|      * @return Whether the handler is enabled for the URL and site. |  | ||||||
|      */ |  | ||||||
|     // eslint-disable-next-line @typescript-eslint/no-unused-vars
 |  | ||||||
|     async isEnabled(siteId: string, url: string, params: Record<string, string>, courseId?: number): Promise<boolean> { |  | ||||||
|         return AddonModLesson.isPluginEnabled(siteId); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const AddonModLessonGradeLinkHandler = makeSingleton(AddonModLessonGradeLinkHandlerService); | export const AddonModLessonGradeLinkHandler = makeSingleton(AddonModLessonGradeLinkHandlerService); | ||||||
|  | |||||||
| @ -66,21 +66,6 @@ export class AddonModLessonIndexLinkHandlerService extends CoreContentLinksModul | |||||||
|         }]; |         }]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Check if the handler is enabled for a certain site (site + user) and a URL. |  | ||||||
|      * If not defined, defaults to true. |  | ||||||
|      * |  | ||||||
|      * @param siteId The site ID. |  | ||||||
|      * @param url The URL to treat. |  | ||||||
|      * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} |  | ||||||
|      * @param courseId Course ID related to the URL. Optional but recommended. |  | ||||||
|      * @return Whether the handler is enabled for the URL and site. |  | ||||||
|      */ |  | ||||||
|     // eslint-disable-next-line @typescript-eslint/no-unused-vars
 |  | ||||||
|     isEnabled(siteId: string, url: string, params: Record<string, string>, courseId?: number): Promise<boolean> { |  | ||||||
|         return AddonModLesson.isPluginEnabled(siteId); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * Navigate to a lesson module (index page) with a fixed password. |      * Navigate to a lesson module (index page) with a fixed password. | ||||||
|      * |      * | ||||||
|  | |||||||
| @ -16,7 +16,6 @@ import { Injectable } from '@angular/core'; | |||||||
| 
 | 
 | ||||||
| import { CoreContentLinksModuleListHandler } from '@features/contentlinks/classes/module-list-handler'; | import { CoreContentLinksModuleListHandler } from '@features/contentlinks/classes/module-list-handler'; | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
| import { AddonModLesson } from '../lesson'; |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Handler to treat links to lesson list page. |  * Handler to treat links to lesson list page. | ||||||
| @ -30,16 +29,6 @@ export class AddonModLessonListLinkHandlerService extends CoreContentLinksModule | |||||||
|         super('AddonModLesson', 'lesson'); |         super('AddonModLesson', 'lesson'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Check if the handler is enabled on a site level. |  | ||||||
|      * |  | ||||||
|      * @return Promise resolved with boolean: whether or not the handler is enabled on a site level. |  | ||||||
|      */ |  | ||||||
|     // eslint-disable-next-line @typescript-eslint/no-unused-vars
 |  | ||||||
|     isEnabled(siteId: string, url: string, params: Record<string, string>, courseId?: number): Promise<boolean> { |  | ||||||
|         return AddonModLesson.isPluginEnabled(siteId); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const AddonModLessonListLinkHandler = makeSingleton(AddonModLessonListLinkHandlerService); | export const AddonModLessonListLinkHandler = makeSingleton(AddonModLessonListLinkHandlerService); | ||||||
|  | |||||||
| @ -18,7 +18,6 @@ import { CoreConstants } from '@/core/constants'; | |||||||
| import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@features/course/services/module-delegate'; | import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@features/course/services/module-delegate'; | ||||||
| import { CoreCourse, CoreCourseAnyModuleData, CoreCourseWSModule } from '@features/course/services/course'; | import { CoreCourse, CoreCourseAnyModuleData, CoreCourseWSModule } from '@features/course/services/course'; | ||||||
| import { CoreCourseModule } from '@features/course/services/course-helper'; | import { CoreCourseModule } from '@features/course/services/course-helper'; | ||||||
| import { AddonModLesson } from '../lesson'; |  | ||||||
| import { AddonModLessonIndexComponent } from '../../components/index'; | import { AddonModLessonIndexComponent } from '../../components/index'; | ||||||
| import { CoreCourseAnyCourseData } from '@features/courses/services/courses'; | import { CoreCourseAnyCourseData } from '@features/courses/services/courses'; | ||||||
| import { CoreNavigationOptions, CoreNavigator } from '@services/navigator'; | import { CoreNavigationOptions, CoreNavigator } from '@services/navigator'; | ||||||
| @ -52,8 +51,8 @@ export class AddonModLessonModuleHandlerService implements CoreCourseModuleHandl | |||||||
|      * |      * | ||||||
|      * @return Promise resolved with boolean: whether or not the handler is enabled on a site level. |      * @return Promise resolved with boolean: whether or not the handler is enabled on a site level. | ||||||
|      */ |      */ | ||||||
|     isEnabled(): Promise<boolean> { |     async isEnabled(): Promise<boolean> { | ||||||
|         return AddonModLesson.isPluginEnabled(); |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -221,15 +221,6 @@ export class AddonModLessonPrefetchHandlerService extends CoreCourseActivityPref | |||||||
|             (accessInfo.preventaccessreasons.length == 1 && AddonModLesson.isPasswordProtected(accessInfo)); |             (accessInfo.preventaccessreasons.length == 1 && AddonModLesson.isPasswordProtected(accessInfo)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Whether or not the handler is enabled on a site level. |  | ||||||
|      * |  | ||||||
|      * @return Promise resolved with a boolean indicating if the handler is enabled. |  | ||||||
|      */ |  | ||||||
|     isEnabled(): Promise<boolean> { |  | ||||||
|         return AddonModLesson.isPluginEnabled(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * @inheritdoc |      * @inheritdoc | ||||||
|      */ |      */ | ||||||
|  | |||||||
| @ -20,7 +20,6 @@ import { CoreCourse } from '@features/course/services/course'; | |||||||
| import { CoreNavigator } from '@services/navigator'; | import { CoreNavigator } from '@services/navigator'; | ||||||
| import { CoreDomUtils } from '@services/utils/dom'; | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
| import { AddonModLesson } from '../lesson'; |  | ||||||
| import { AddonModLessonModuleHandlerService } from './module'; | import { AddonModLessonModuleHandlerService } from './module'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -81,7 +80,7 @@ export class AddonModLessonReportLinkHandlerService extends CoreContentLinksHand | |||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return AddonModLesson.isPluginEnabled(siteId); |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -2710,19 +2710,6 @@ export class AddonModLessonProvider { | |||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Return whether or not the plugin is enabled in a certain site. Plugin is enabled if the lesson WS are available. |  | ||||||
|      * |  | ||||||
|      * @param siteId Site ID. If not defined, current site. |  | ||||||
|      * @return Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise. |  | ||||||
|      */ |  | ||||||
|     async isPluginEnabled(siteId?: string): Promise<boolean> { |  | ||||||
|         const site = await CoreSites.getSite(siteId); |  | ||||||
| 
 |  | ||||||
|         // All WS were introduced at the same time so checking one is enough.
 |  | ||||||
|         return site.wsAvailable('mod_lesson_get_lesson_access_information'); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * Check if a page is a question page or a content page. |      * Check if a page is a question page or a content page. | ||||||
|      * |      * | ||||||
|  | |||||||
| @ -331,7 +331,7 @@ export type AddonModLtiLti = { | |||||||
|     name: string; // LTI name.
 |     name: string; // LTI name.
 | ||||||
|     intro?: string; // The LTI intro.
 |     intro?: string; // The LTI intro.
 | ||||||
|     introformat?: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 |     introformat?: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||||
|     introfiles?: CoreWSExternalFile[]; // @since 3.2.
 |     introfiles?: CoreWSExternalFile[]; | ||||||
|     timecreated?: number; // Time of creation.
 |     timecreated?: number; // Time of creation.
 | ||||||
|     timemodified?: number; // Time of last modification.
 |     timemodified?: number; // Time of last modification.
 | ||||||
|     typeid?: number; // Type id.
 |     typeid?: number; // Type id.
 | ||||||
|  | |||||||
| @ -15,7 +15,7 @@ | |||||||
| import { Component, OnInit, Optional } from '@angular/core'; | import { Component, OnInit, Optional } from '@angular/core'; | ||||||
| import { CoreCourseModuleMainResourceComponent } from '@features/course/classes/main-resource-component'; | import { CoreCourseModuleMainResourceComponent } from '@features/course/classes/main-resource-component'; | ||||||
| import { CoreCourseContentsPage } from '@features/course/pages/contents/contents'; | import { CoreCourseContentsPage } from '@features/course/pages/contents/contents'; | ||||||
| import { CoreCourse, CoreCourseWSModule } from '@features/course/services/course'; | import { CoreCourse } from '@features/course/services/course'; | ||||||
| import { CoreTextUtils } from '@services/utils/text'; | import { CoreTextUtils } from '@services/utils/text'; | ||||||
| import { CoreUtils } from '@services/utils/utils'; | import { CoreUtils } from '@services/utils/utils'; | ||||||
| import { AddonModPageProvider, AddonModPagePage, AddonModPage } from '../../services/page'; | import { AddonModPageProvider, AddonModPagePage, AddonModPage } from '../../services/page'; | ||||||
| @ -32,12 +32,11 @@ import { AddonModPageHelper } from '../../services/page-helper'; | |||||||
| export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComponent implements OnInit { | export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComponent implements OnInit { | ||||||
| 
 | 
 | ||||||
|     component = AddonModPageProvider.COMPONENT; |     component = AddonModPageProvider.COMPONENT; | ||||||
|     canGetPage = false; |  | ||||||
|     contents?: string; |     contents?: string; | ||||||
|     displayDescription = true; |     displayDescription = true; | ||||||
|     displayTimemodified = true; |     displayTimemodified = true; | ||||||
|     timemodified?: number; |     timemodified?: number; | ||||||
|     page?: CoreCourseWSModule | AddonModPagePage; |     page?: AddonModPagePage; | ||||||
|     warning?: string; |     warning?: string; | ||||||
| 
 | 
 | ||||||
|     protected fetchContentDefaultError = 'addon.mod_page.errorwhileloadingthepage'; |     protected fetchContentDefaultError = 'addon.mod_page.errorwhileloadingthepage'; | ||||||
| @ -52,8 +51,6 @@ export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComp | |||||||
|     async ngOnInit(): Promise<void> { |     async ngOnInit(): Promise<void> { | ||||||
|         super.ngOnInit(); |         super.ngOnInit(); | ||||||
| 
 | 
 | ||||||
|         this.canGetPage = AddonModPage.isGetPageWSAvailable(); |  | ||||||
| 
 |  | ||||||
|         await this.loadContent(); |         await this.loadContent(); | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
| @ -103,21 +100,13 @@ export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComp | |||||||
|      */ |      */ | ||||||
|     protected async loadPageData(): Promise<void> { |     protected async loadPageData(): Promise<void> { | ||||||
|         // Get latest title, description and some extra data. Data should've been updated in download.
 |         // Get latest title, description and some extra data. Data should've been updated in download.
 | ||||||
|         const page = this.canGetPage ? |         this.page = await AddonModPage.getPageData(this.courseId, this.module.id); | ||||||
|             await AddonModPage.getPageData(this.courseId, this.module.id) : |  | ||||||
|             await CoreCourse.getModule(this.module.id, this.courseId); |  | ||||||
| 
 | 
 | ||||||
|         this.description = 'intro' in page ? page.intro : page.description; |         this.description = this.page.intro; | ||||||
|         this.dataRetrieved.emit(page); |         this.dataRetrieved.emit(this.page); | ||||||
| 
 |  | ||||||
|         if (!this.canGetPage) { |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         this.page = page; |  | ||||||
| 
 | 
 | ||||||
|         // Check if description and timemodified should be displayed.
 |         // Check if description and timemodified should be displayed.
 | ||||||
|         if ('displayoptions' in this.page) { |         if (this.page.displayoptions) { | ||||||
|             const options: Record<string, string | boolean> = |             const options: Record<string, string | boolean> = | ||||||
|                 CoreTextUtils.unserialize(this.page.displayoptions) || {}; |                 CoreTextUtils.unserialize(this.page.displayoptions) || {}; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -42,10 +42,7 @@ export class AddonModPagePrefetchHandlerService extends CoreCourseResourcePrefet | |||||||
|         const promises: Promise<unknown>[] = []; |         const promises: Promise<unknown>[] = []; | ||||||
| 
 | 
 | ||||||
|         promises.push(super.downloadOrPrefetch(module, courseId, prefetch)); |         promises.push(super.downloadOrPrefetch(module, courseId, prefetch)); | ||||||
| 
 |  | ||||||
|         if (AddonModPage.isGetPageWSAvailable()) { |  | ||||||
|         promises.push(AddonModPage.getPageData(courseId, module.id)); |         promises.push(AddonModPage.getPageData(courseId, module.id)); | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         await Promise.all(promises); |         await Promise.all(promises); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -124,16 +124,6 @@ export class AddonModPageProvider { | |||||||
|         await site.invalidateWsCacheForKey(this.getPageCacheKey(courseId)); |         await site.invalidateWsCacheForKey(this.getPageCacheKey(courseId)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Returns whether or not getPage WS available or not. |  | ||||||
|      * |  | ||||||
|      * @return If WS is available. |  | ||||||
|      * @since 3.3 |  | ||||||
|      */ |  | ||||||
|     isGetPageWSAvailable(): boolean { |  | ||||||
|         return CoreSites.wsAvailableInCurrentSite('mod_page_get_pages_by_courses'); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * Return whether or not the plugin is enabled. |      * Return whether or not the plugin is enabled. | ||||||
|      * |      * | ||||||
|  | |||||||
| @ -18,7 +18,6 @@ import { Component, OnDestroy, OnInit, Optional } from '@angular/core'; | |||||||
| import { CoreCourseModuleMainActivityComponent } from '@features/course/classes/main-activity-component'; | import { CoreCourseModuleMainActivityComponent } from '@features/course/classes/main-activity-component'; | ||||||
| import { CoreCourseContentsPage } from '@features/course/pages/contents/contents'; | import { CoreCourseContentsPage } from '@features/course/pages/contents/contents'; | ||||||
| import { CoreCourse } from '@features/course/services/course'; | import { CoreCourse } from '@features/course/services/course'; | ||||||
| import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate'; |  | ||||||
| import { CoreQuestionBehaviourDelegate } from '@features/question/services/behaviour-delegate'; | import { CoreQuestionBehaviourDelegate } from '@features/question/services/behaviour-delegate'; | ||||||
| import { IonContent } from '@ionic/angular'; | import { IonContent } from '@ionic/angular'; | ||||||
| import { CoreNavigator } from '@services/navigator'; | import { CoreNavigator } from '@services/navigator'; | ||||||
| @ -154,7 +153,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp | |||||||
|         // If the site doesn't support check updates, always prefetch it because we cannot tell if there's something new.
 |         // If the site doesn't support check updates, always prefetch it because we cannot tell if there's something new.
 | ||||||
|         const isDownloaded = this.currentStatus == CoreConstants.DOWNLOADED; |         const isDownloaded = this.currentStatus == CoreConstants.DOWNLOADED; | ||||||
| 
 | 
 | ||||||
|         if (isDownloaded && CoreCourseModulePrefetchDelegate.canCheckUpdates()) { |         if (isDownloaded) { | ||||||
|             // Already downloaded, open it.
 |             // Already downloaded, open it.
 | ||||||
|             return this.openQuiz(); |             return this.openQuiz(); | ||||||
|         } |         } | ||||||
| @ -168,7 +167,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp | |||||||
|             // Success downloading, open quiz.
 |             // Success downloading, open quiz.
 | ||||||
|             this.openQuiz(); |             this.openQuiz(); | ||||||
|         } catch (error) { |         } catch (error) { | ||||||
|             if (this.hasOffline || (isDownloaded && !CoreCourseModulePrefetchDelegate.canCheckUpdates())) { |             if (this.hasOffline) { | ||||||
|                 // Error downloading but there is something offline, allow continuing it.
 |                 // Error downloading but there is something offline, allow continuing it.
 | ||||||
|                 // If the site doesn't support check updates, continue too because we cannot tell if there's something new.
 |                 // If the site doesn't support check updates, continue too because we cannot tell if there's something new.
 | ||||||
|                 this.openQuiz(); |                 this.openQuiz(); | ||||||
| @ -647,9 +646,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp | |||||||
| 
 | 
 | ||||||
|             if (data) { |             if (data) { | ||||||
|                 this.gradebookData = { |                 this.gradebookData = { | ||||||
|                     grade: 'graderaw' in data && data.graderaw !== undefined && data.graderaw !== null ? |                     grade: data.graderaw ?? (data.grade !== undefined && data.grade !== null ? Number(data.grade) : undefined), | ||||||
|                         data.graderaw : |  | ||||||
|                         (data.grade !== undefined && data.grade !== null ? Number(data.grade) : undefined), |  | ||||||
|                     feedback: data.feedback, |                     feedback: data.feedback, | ||||||
|                 }; |                 }; | ||||||
|             } |             } | ||||||
|  | |||||||
| @ -111,7 +111,6 @@ export class AddonModQuizPrefetchHandlerService extends CoreCourseActivityPrefet | |||||||
|         attempts: AddonModQuizAttemptWSData[], |         attempts: AddonModQuizAttemptWSData[], | ||||||
|         siteId?: string, |         siteId?: string, | ||||||
|     ): Promise<CoreWSFile[]> { |     ): Promise<CoreWSFile[]> { | ||||||
|         const getInlineFiles = CoreSites.getCurrentSite()?.isVersionGreaterEqualThan('3.2'); |  | ||||||
|         let files: CoreWSFile[] = []; |         let files: CoreWSFile[] = []; | ||||||
| 
 | 
 | ||||||
|         await Promise.all(attempts.map(async (attempt) => { |         await Promise.all(attempts.map(async (attempt) => { | ||||||
| @ -131,12 +130,8 @@ export class AddonModQuizPrefetchHandlerService extends CoreCourseActivityPrefet | |||||||
|                 siteId, |                 siteId, | ||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
|             if (getInlineFiles && feedback.feedbackinlinefiles?.length) { |             if (feedback.feedbackinlinefiles?.length) { | ||||||
|                 files = files.concat(feedback.feedbackinlinefiles); |                 files = files.concat(feedback.feedbackinlinefiles); | ||||||
|             } else if (feedback.feedbacktext && !getInlineFiles) { |  | ||||||
|                 files = files.concat( |  | ||||||
|                     CoreFilepool.extractDownloadableFilesFromHtmlAsFakeFileObjects(feedback.feedbacktext), |  | ||||||
|                 ); |  | ||||||
|             } |             } | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
| @ -526,7 +521,7 @@ export class AddonModQuizPrefetchHandlerService extends CoreCourseActivityPrefet | |||||||
|         try { |         try { | ||||||
|             const gradebookData = await AddonModQuiz.getGradeFromGradebook(quiz.course, quiz.coursemodule, true, siteId); |             const gradebookData = await AddonModQuiz.getGradeFromGradebook(quiz.course, quiz.coursemodule, true, siteId); | ||||||
| 
 | 
 | ||||||
|             if (gradebookData && 'graderaw' in gradebookData && gradebookData.graderaw !== undefined) { |             if (gradebookData && gradebookData.graderaw !== undefined) { | ||||||
|                 await AddonModQuiz.getFeedbackForGrade(quiz.id, gradebookData.graderaw, modOptions); |                 await AddonModQuiz.getFeedbackForGrade(quiz.id, gradebookData.graderaw, modOptions); | ||||||
|             } |             } | ||||||
|         } catch { |         } catch { | ||||||
|  | |||||||
| @ -19,7 +19,7 @@ import { CoreWSError } from '@classes/errors/wserror'; | |||||||
| import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; | import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; | ||||||
| import { CoreCourseCommonModWSOptions } from '@features/course/services/course'; | import { CoreCourseCommonModWSOptions } from '@features/course/services/course'; | ||||||
| import { CoreCourseLogHelper } from '@features/course/services/log-helper'; | import { CoreCourseLogHelper } from '@features/course/services/log-helper'; | ||||||
| import { CoreGradesFormattedItem, CoreGradesFormattedRow, CoreGradesHelper } from '@features/grades/services/grades-helper'; | import { CoreGradesFormattedItem, CoreGradesHelper } from '@features/grades/services/grades-helper'; | ||||||
| import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications'; | import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications'; | ||||||
| import { | import { | ||||||
|     CoreQuestion, |     CoreQuestion, | ||||||
| @ -647,7 +647,7 @@ export class AddonModQuizProvider { | |||||||
|         ignoreCache?: boolean, |         ignoreCache?: boolean, | ||||||
|         siteId?: string, |         siteId?: string, | ||||||
|         userId?: number, |         userId?: number, | ||||||
|     ): Promise<CoreGradesFormattedItem | CoreGradesFormattedRow | undefined> { |     ): Promise<CoreGradesFormattedItem | undefined> { | ||||||
| 
 | 
 | ||||||
|         const items = await CoreGradesHelper.getGradeModuleItems( |         const items = await CoreGradesHelper.getGradeModuleItems( | ||||||
|             courseId, |             courseId, | ||||||
|  | |||||||
| @ -16,7 +16,7 @@ import { Component, OnDestroy, OnInit, Optional } from '@angular/core'; | |||||||
| import { CoreError } from '@classes/errors/error'; | import { CoreError } from '@classes/errors/error'; | ||||||
| import { CoreCourseModuleMainResourceComponent } from '@features/course/classes/main-resource-component'; | import { CoreCourseModuleMainResourceComponent } from '@features/course/classes/main-resource-component'; | ||||||
| import { CoreCourseContentsPage } from '@features/course/pages/contents/contents'; | import { CoreCourseContentsPage } from '@features/course/pages/contents/contents'; | ||||||
| import { CoreCourse, CoreCourseWSModule } from '@features/course/services/course'; | import { CoreCourse } from '@features/course/services/course'; | ||||||
| import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate'; | import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate'; | ||||||
| import { CoreApp } from '@services/app'; | import { CoreApp } from '@services/app'; | ||||||
| import { CoreFileHelper } from '@services/file-helper'; | import { CoreFileHelper } from '@services/file-helper'; | ||||||
| @ -30,7 +30,6 @@ import { | |||||||
|     AddonModResource, |     AddonModResource, | ||||||
|     AddonModResourceCustomData, |     AddonModResourceCustomData, | ||||||
|     AddonModResourceProvider, |     AddonModResourceProvider, | ||||||
|     AddonModResourceResource, |  | ||||||
| } from '../../services/resource'; | } from '../../services/resource'; | ||||||
| import { AddonModResourceHelper } from '../../services/resource-helper'; | import { AddonModResourceHelper } from '../../services/resource-helper'; | ||||||
| 
 | 
 | ||||||
| @ -45,7 +44,6 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource | |||||||
| 
 | 
 | ||||||
|     component = AddonModResourceProvider.COMPONENT; |     component = AddonModResourceProvider.COMPONENT; | ||||||
| 
 | 
 | ||||||
|     canGetResource = false; |  | ||||||
|     mode = ''; |     mode = ''; | ||||||
|     src = ''; |     src = ''; | ||||||
|     contentText = ''; |     contentText = ''; | ||||||
| @ -69,7 +67,6 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource | |||||||
|     async ngOnInit(): Promise<void> { |     async ngOnInit(): Promise<void> { | ||||||
|         super.ngOnInit(); |         super.ngOnInit(); | ||||||
| 
 | 
 | ||||||
|         this.canGetResource = AddonModResource.isGetResourceWSAvailable(); |  | ||||||
|         this.isIOS = CoreApp.isIOS(); |         this.isIOS = CoreApp.isIOS(); | ||||||
|         this.isOnline = CoreApp.isOnline(); |         this.isOnline = CoreApp.isOnline(); | ||||||
| 
 | 
 | ||||||
| @ -110,26 +107,17 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource | |||||||
|             throw new CoreError(Translate.instant('core.filenotfound')); |             throw new CoreError(Translate.instant('core.filenotfound')); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         let resource: AddonModResourceResource | CoreCourseWSModule | undefined; |  | ||||||
|         let options: AddonModResourceCustomData = {}; |  | ||||||
|         let hasCalledDownloadResource = false; |         let hasCalledDownloadResource = false; | ||||||
| 
 | 
 | ||||||
|         // Get the resource instance to get the latest name/description and to know if it's embedded.
 |         // Get the resource instance to get the latest name/description and to know if it's embedded.
 | ||||||
|         if (this.canGetResource) { |         const resource = await AddonModResource.getResourceData(this.courseId, this.module.id); | ||||||
|             resource = await AddonModResource.getResourceData(this.courseId, this.module.id); |  | ||||||
|         this.description = resource.intro || ''; |         this.description = resource.intro || ''; | ||||||
|             options = resource.displayoptions ? CoreTextUtils.unserialize(resource.displayoptions) : {}; |         const options: AddonModResourceCustomData = | ||||||
|         } else { |             resource.displayoptions ? CoreTextUtils.unserialize(resource.displayoptions) : {}; | ||||||
|             resource = await CoreCourse.getModule(this.module.id, this.courseId); |  | ||||||
|             this.description = resource.description || ''; |  | ||||||
|             options = resource.customdata ? CoreTextUtils.unserialize(CoreTextUtils.parseJSON(resource.customdata)) : {}; |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             if (resource) { |  | ||||||
|             this.displayDescription = typeof options.printintro == 'undefined' || !!options.printintro; |             this.displayDescription = typeof options.printintro == 'undefined' || !!options.printintro; | ||||||
|             this.dataRetrieved.emit(resource); |             this.dataRetrieved.emit(resource); | ||||||
|             } |  | ||||||
| 
 | 
 | ||||||
|             if (AddonModResourceHelper.isDisplayedInIframe(this.module)) { |             if (AddonModResourceHelper.isDisplayedInIframe(this.module)) { | ||||||
|                 hasCalledDownloadResource = true; |                 hasCalledDownloadResource = true; | ||||||
|  | |||||||
| @ -156,7 +156,7 @@ export class AddonModResourceModuleHandlerService implements CoreCourseModuleHan | |||||||
| 
 | 
 | ||||||
|         if ('customdata' in module && typeof module.customdata != 'undefined') { |         if ('customdata' in module && typeof module.customdata != 'undefined') { | ||||||
|             options = CoreTextUtils.unserialize(CoreTextUtils.parseJSON(module.customdata)); |             options = CoreTextUtils.unserialize(CoreTextUtils.parseJSON(module.customdata)); | ||||||
|         } else if (AddonModResource.isGetResourceWSAvailable()) { |         } else { | ||||||
|             // Get the resource data.
 |             // Get the resource data.
 | ||||||
|             promises.push(AddonModResource.getResourceData(courseId, module.id).then((info) => { |             promises.push(AddonModResource.getResourceData(courseId, module.id).then((info) => { | ||||||
|                 infoFiles = info.contentfiles; |                 infoFiles = info.contentfiles; | ||||||
|  | |||||||
| @ -67,10 +67,7 @@ export class AddonModResourcePrefetchHandlerService extends CoreCourseResourcePr | |||||||
|         const promises: Promise<unknown>[] = []; |         const promises: Promise<unknown>[] = []; | ||||||
| 
 | 
 | ||||||
|         promises.push(super.downloadOrPrefetch(module, courseId, prefetch, dirPath)); |         promises.push(super.downloadOrPrefetch(module, courseId, prefetch, dirPath)); | ||||||
| 
 |  | ||||||
|         if (AddonModResource.isGetResourceWSAvailable()) { |  | ||||||
|         promises.push(AddonModResource.getResourceData(courseId, module.id)); |         promises.push(AddonModResource.getResourceData(courseId, module.id)); | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         await Promise.all(promises); |         await Promise.all(promises); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -130,16 +130,6 @@ export class AddonModResourceProvider { | |||||||
|         await site.invalidateWsCacheForKey(this.getResourceCacheKey(courseId)); |         await site.invalidateWsCacheForKey(this.getResourceCacheKey(courseId)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Returns whether or not getResource WS available or not. |  | ||||||
|      * |  | ||||||
|      * @return If WS is abalaible. |  | ||||||
|      * @since 3.3 |  | ||||||
|      */ |  | ||||||
|     isGetResourceWSAvailable(): boolean { |  | ||||||
|         return CoreSites.wsAvailableInCurrentSite('mod_resource_get_resources_by_courses'); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * Return whether or not the plugin is enabled. |      * Return whether or not the plugin is enabled. | ||||||
|      * |      * | ||||||
|  | |||||||
| @ -1612,11 +1612,6 @@ export class AddonModScormProvider { | |||||||
|         }; |         }; | ||||||
|         const wsFunction = 'mod_scorm_insert_scorm_tracks'; |         const wsFunction = 'mod_scorm_insert_scorm_tracks'; | ||||||
| 
 | 
 | ||||||
|         // Check if the method is available, use a prefixed version if possible.
 |  | ||||||
|         if (!currentSite.wsAvailable(wsFunction, false)) { |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         try { |         try { | ||||||
|             const response = CoreWS.syncCall<AddonModScormInsertScormTracksWSResponse>(wsFunction, params, preSets); |             const response = CoreWS.syncCall<AddonModScormInsertScormTracksWSResponse>(wsFunction, params, preSets); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -322,7 +322,7 @@ export type AddonModSurveySurvey = { | |||||||
|     name: string; // Survey name.
 |     name: string; // Survey name.
 | ||||||
|     intro?: string; // The Survey intro.
 |     intro?: string; // The Survey intro.
 | ||||||
|     introformat?: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 |     introformat?: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||||
|     introfiles?: CoreWSExternalFile[]; // @since 3.2.
 |     introfiles?: CoreWSExternalFile[]; | ||||||
|     template?: number; // Survey type.
 |     template?: number; // Survey type.
 | ||||||
|     days?: number; // Days.
 |     days?: number; // Days.
 | ||||||
|     questions?: string; // Question ids.
 |     questions?: string; // Question ids.
 | ||||||
|  | |||||||
| @ -36,7 +36,6 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo | |||||||
| 
 | 
 | ||||||
|     component = AddonModUrlProvider.COMPONENT; |     component = AddonModUrlProvider.COMPONENT; | ||||||
| 
 | 
 | ||||||
|     canGetUrl = false; |  | ||||||
|     url?: string; |     url?: string; | ||||||
|     name?: string; |     name?: string; | ||||||
|     shouldEmbed = false; |     shouldEmbed = false; | ||||||
| @ -58,8 +57,6 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo | |||||||
|     async ngOnInit(): Promise<void> { |     async ngOnInit(): Promise<void> { | ||||||
|         super.ngOnInit(); |         super.ngOnInit(); | ||||||
| 
 | 
 | ||||||
|         this.canGetUrl = AddonModUrl.isGetUrlWSAvailable(); |  | ||||||
| 
 |  | ||||||
|         await this.loadContent(); |         await this.loadContent(); | ||||||
| 
 | 
 | ||||||
|         if ((this.shouldIframe || |         if ((this.shouldIframe || | ||||||
| @ -86,9 +83,6 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo | |||||||
|      */ |      */ | ||||||
|     protected async fetchContent(refresh = false): Promise<void> { |     protected async fetchContent(refresh = false): Promise<void> { | ||||||
|         try { |         try { | ||||||
|             if (!this.canGetUrl) { |  | ||||||
|                 throw null; |  | ||||||
|             } |  | ||||||
|             // Fetch the module data.
 |             // Fetch the module data.
 | ||||||
|             const url = await AddonModUrl.getUrl(this.courseId, this.module.id); |             const url = await AddonModUrl.getUrl(this.courseId, this.module.id); | ||||||
| 
 | 
 | ||||||
| @ -110,7 +104,7 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo | |||||||
|             await this.calculateDisplayOptions(url); |             await this.calculateDisplayOptions(url); | ||||||
| 
 | 
 | ||||||
|         } catch { |         } catch { | ||||||
|             // Fallback in case is not prefetched or not available.
 |             // Fallback in case is not prefetched.
 | ||||||
|             const mod = |             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'); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -175,7 +175,7 @@ export class AddonModUrlModuleHandlerService implements CoreCourseModuleHandler | |||||||
|             if (canHandle) { |             if (canHandle) { | ||||||
|                 // URL handled by the app, open it directly.
 |                 // URL handled by the app, open it directly.
 | ||||||
|                 return true; |                 return true; | ||||||
|             } else if (AddonModUrl.isGetUrlWSAvailable()) { |             } else { | ||||||
|                 // Not handled by the app, check the display type.
 |                 // Not handled by the app, check the display type.
 | ||||||
|                 const url = await CoreUtils.ignoreErrors(AddonModUrl.getUrl(courseId, module.id)); |                 const url = await CoreUtils.ignoreErrors(AddonModUrl.getUrl(courseId, module.id)); | ||||||
|                 const displayType = AddonModUrl.getFinalDisplayType(url); |                 const displayType = AddonModUrl.getFinalDisplayType(url); | ||||||
| @ -183,8 +183,6 @@ export class AddonModUrlModuleHandlerService implements CoreCourseModuleHandler | |||||||
|                 return displayType == CoreConstants.RESOURCELIB_DISPLAY_OPEN || |                 return displayType == CoreConstants.RESOURCELIB_DISPLAY_OPEN || | ||||||
|                     displayType == CoreConstants.RESOURCELIB_DISPLAY_POPUP; |                     displayType == CoreConstants.RESOURCELIB_DISPLAY_POPUP; | ||||||
|             } |             } | ||||||
| 
 |  | ||||||
|             return false; |  | ||||||
|         } catch { |         } catch { | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -206,16 +206,6 @@ export class AddonModUrlProvider { | |||||||
|         await site.invalidateWsCacheForKey(this.getUrlCacheKey(courseId)); |         await site.invalidateWsCacheForKey(this.getUrlCacheKey(courseId)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Returns whether or not getUrl WS available or not. |  | ||||||
|      * |  | ||||||
|      * @return If WS is abalaible. |  | ||||||
|      * @since 3.3 |  | ||||||
|      */ |  | ||||||
|     isGetUrlWSAvailable(): boolean { |  | ||||||
|         return CoreSites.wsAvailableInCurrentSite('mod_url_get_urls_by_courses'); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * Report the url as being viewed. |      * Report the url as being viewed. | ||||||
|      * |      * | ||||||
|  | |||||||
| @ -178,9 +178,7 @@ export class AddonModWikiProvider { | |||||||
|         if (section) { |         if (section) { | ||||||
|             params.section = section; |             params.section = section; | ||||||
|         } |         } | ||||||
| 
 |         if (lockOnly) { | ||||||
|         // This parameter requires Moodle 3.2. It saves network usage.
 |  | ||||||
|         if (lockOnly && site.isVersionGreaterEqualThan('3.2')) { |  | ||||||
|             params.lockonly = true; |             params.lockonly = true; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -15,7 +15,7 @@ | |||||||
| import { Injectable } from '@angular/core'; | import { Injectable } from '@angular/core'; | ||||||
| import { CoreContentLinksModuleIndexHandler } from '@features/contentlinks/classes/module-index-handler'; | import { CoreContentLinksModuleIndexHandler } from '@features/contentlinks/classes/module-index-handler'; | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
| import { AddonModWorkshopProvider, AddonModWorkshop } from '../workshop'; | import { AddonModWorkshopProvider } from '../workshop'; | ||||||
| /** | /** | ||||||
|  * Handler to treat links to workshop. |  * Handler to treat links to workshop. | ||||||
|  */ |  */ | ||||||
| @ -28,12 +28,5 @@ export class AddonModWorkshopIndexLinkHandlerService extends CoreContentLinksMod | |||||||
|         super(AddonModWorkshopProvider.COMPONENT, 'workshop', 'w'); |         super(AddonModWorkshopProvider.COMPONENT, 'workshop', 'w'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * @inheritdoc |  | ||||||
|      */ |  | ||||||
|     isEnabled(siteId: string): Promise<boolean> { |  | ||||||
|         return AddonModWorkshop.isPluginEnabled(siteId); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } | } | ||||||
| export const AddonModWorkshopIndexLinkHandler = makeSingleton(AddonModWorkshopIndexLinkHandlerService); | export const AddonModWorkshopIndexLinkHandler = makeSingleton(AddonModWorkshopIndexLinkHandlerService); | ||||||
|  | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user