MOBILE-3819 core: Remove code specific for Moodle 3.1-3.4

main
Dani Palou 2021-09-03 11:51:26 +02:00
parent d944cb1978
commit 68e57c0f17
147 changed files with 436 additions and 2398 deletions

View File

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

View File

@ -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,

View File

@ -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,

View File

@ -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');
} }
/** /**

View File

@ -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');
} }
/** /**

View File

@ -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: () =>

View File

@ -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"

View File

@ -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;
} }

View File

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

View File

@ -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 {}

View File

@ -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();
}
}

View File

@ -1,5 +0,0 @@
:host {
ion-note {
max-width: 30%;
}
}

View File

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

View File

@ -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',
}; };
} }

View File

@ -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;
});
} }
} }

View File

@ -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);

View File

@ -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;
} }
/** /**

View File

@ -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),
}, },
{ {

View File

@ -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, {

View File

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

View File

@ -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>[] = [];

View File

@ -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();
} }
/** /**

View File

@ -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();
} }
/** /**

View File

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

View File

@ -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"

View File

@ -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;
} }

View File

@ -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);

View File

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

View File

@ -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(

View File

@ -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();

View File

@ -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(

View File

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

View File

@ -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;
} }
/** /**

View File

@ -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;
})); }));

View File

@ -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,
),
); );
} }

View File

@ -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();
} }
/** /**

View File

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

View File

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

View File

@ -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);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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;
} }
} }

View File

@ -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;
} }
} }

View File

@ -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;
} }
} }

View File

@ -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);

View File

@ -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);

View File

@ -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;
} }
/** /**

View File

@ -203,13 +203,6 @@ export class AddonModDataPrefetchHandlerService extends CoreCourseActivityPrefet
return true; return true;
} }
/**
* @inheritdoc
*/
async isEnabled(): Promise<boolean> {
return AddonModData.isPluginEnabled();
}
/** /**
* @inheritdoc * @inheritdoc
*/ */

View File

@ -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;
} }
} }

View File

@ -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;
} }
/** /**

View File

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

View File

@ -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;
} }
} }

View File

@ -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;
} }
} }

View File

@ -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);

View File

@ -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);

View File

@ -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;
} }
/** /**

View File

@ -110,13 +110,6 @@ export class AddonModFeedbackPrefetchHandlerService extends CoreCourseActivityPr
return accessData.isopen; return accessData.isopen;
} }
/**
* @inheritdoc
*/
isEnabled(): Promise<boolean> {
return AddonModFeedback.isPluginEnabled();
}
/** /**
* @inheritdoc * @inheritdoc
*/ */

View File

@ -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;
} }
} }

View File

@ -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;

View File

@ -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;
} }
} }

View File

@ -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;
} }
} }

View File

@ -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);

View File

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

View File

@ -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);
} }

View File

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

View File

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

View File

@ -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);

View File

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

View File

@ -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));
} }
}); });

View File

@ -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;
} }

View File

@ -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);
} }

View File

@ -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);

View File

@ -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);

View File

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

View File

@ -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);

View File

@ -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;
} }
/** /**

View File

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

View File

@ -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;
} }
/** /**

View File

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

View File

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

View File

@ -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) || {};

View File

@ -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);
} }

View File

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

View File

@ -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,
}; };
} }

View File

@ -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 {

View File

@ -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,

View File

@ -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;

View File

@ -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;

View File

@ -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);
} }

View File

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

View File

@ -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);

View File

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

View File

@ -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');

View File

@ -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;
} }

View File

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

View File

@ -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;
} }

View File

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