forked from EVOgeek/Vmeda.Online
		
	
						commit
						c6714285b0
					
				| @ -104,10 +104,12 @@ | ||||
|   "addon.calendar.currentmonth": "local_moodlemobileapp", | ||||
|   "addon.calendar.daynext": "calendar", | ||||
|   "addon.calendar.dayprev": "calendar", | ||||
|   "addon.calendar.dayviewtitle": "calendar", | ||||
|   "addon.calendar.defaultnotificationtime": "local_moodlemobileapp", | ||||
|   "addon.calendar.deleteallevents": "calendar", | ||||
|   "addon.calendar.deleteevent": "calendar", | ||||
|   "addon.calendar.deleteoneevent": "calendar", | ||||
|   "addon.calendar.detailedmonthviewtitle": "calendar", | ||||
|   "addon.calendar.durationminutes": "calendar", | ||||
|   "addon.calendar.durationnone": "calendar", | ||||
|   "addon.calendar.durationuntil": "calendar", | ||||
| @ -369,6 +371,7 @@ | ||||
|   "addon.mod_assign.gradelocked": "assign", | ||||
|   "addon.mod_assign.gradenotsynced": "local_moodlemobileapp", | ||||
|   "addon.mod_assign.gradeoutof": "assign", | ||||
|   "addon.mod_assign.grading": "assign", | ||||
|   "addon.mod_assign.gradingstatus": "assign", | ||||
|   "addon.mod_assign.groupsubmissionsettings": "assign", | ||||
|   "addon.mod_assign.hiddenuser": "assign", | ||||
| @ -425,6 +428,7 @@ | ||||
|   "addon.mod_assign.submittedlate": "assign", | ||||
|   "addon.mod_assign.submittedovertime": "assign", | ||||
|   "addon.mod_assign.submittedundertime": "assign", | ||||
|   "addon.mod_assign.subpagetitle": "assign", | ||||
|   "addon.mod_assign.syncblockedusercomponent": "local_moodlemobileapp", | ||||
|   "addon.mod_assign.timelimit": "assign", | ||||
|   "addon.mod_assign.timemodified": "assign", | ||||
| @ -2235,6 +2239,7 @@ | ||||
|   "core.play": "local_moodlemobileapp", | ||||
|   "core.previous": "moodle", | ||||
|   "core.proceed": "moodle", | ||||
|   "core.publicprofile": "moodle", | ||||
|   "core.pulltorefresh": "local_moodlemobileapp", | ||||
|   "core.qrscanner": "local_moodlemobileapp", | ||||
|   "core.question.answer": "question", | ||||
| @ -2347,9 +2352,9 @@ | ||||
|   "core.settings.disabled": "lesson", | ||||
|   "core.settings.disallowed": "message", | ||||
|   "core.settings.displayformat": "local_moodlemobileapp", | ||||
|   "core.settings.enableanalytics": "local_moodlemobileapp", | ||||
|   "core.settings.enableanalyticsdescription": "local_moodlemobileapp", | ||||
|   "core.settings.enabledownloadsection": "local_moodlemobileapp", | ||||
|   "core.settings.enablefirebaseanalytics": "local_moodlemobileapp", | ||||
|   "core.settings.enablefirebaseanalyticsdescription": "local_moodlemobileapp", | ||||
|   "core.settings.enablerichtexteditor": "local_moodlemobileapp", | ||||
|   "core.settings.enablerichtexteditordescription": "local_moodlemobileapp", | ||||
|   "core.settings.encryptedpushsupported": "local_moodlemobileapp", | ||||
|  | ||||
| @ -26,6 +26,8 @@ import { ActivatedRoute } from '@angular/router'; | ||||
| import { CoreSwipeNavigationItemsManager } from '@classes/items-management/swipe-navigation-items-manager'; | ||||
| import { AddonBadgesUserBadgesSource } from '@addons/badges/classes/user-badges-source'; | ||||
| import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| import { CoreTime } from '@singletons/time'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays the list of calendar events. | ||||
| @ -38,6 +40,7 @@ export class AddonBadgesIssuedBadgePage implements OnInit, OnDestroy { | ||||
| 
 | ||||
|     protected badgeHash = ''; | ||||
|     protected userId!: number; | ||||
|     protected logView: (badge: AddonBadgesUserBadge) => void; | ||||
| 
 | ||||
|     courseId = 0; | ||||
|     user?: CoreUserProfile; | ||||
| @ -58,6 +61,16 @@ export class AddonBadgesIssuedBadgePage implements OnInit, OnDestroy { | ||||
|         ); | ||||
| 
 | ||||
|         this.badges = new CoreSwipeNavigationItemsManager(source); | ||||
| 
 | ||||
|         this.logView = CoreTime.once((badge) => { | ||||
|             CoreAnalytics.logEvent({ | ||||
|                 type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|                 ws: 'core_badges_view_user_badges', | ||||
|                 name: badge.name, | ||||
|                 data: { id: badge.uniquehash, category: 'badges' }, | ||||
|                 url: `/badges/badge.php?hash=${badge.uniquehash}`, | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -105,6 +118,8 @@ export class AddonBadgesIssuedBadgePage implements OnInit, OnDestroy { | ||||
|                     this.course = undefined; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             this.logView(badge); | ||||
|         } catch (message) { | ||||
|             CoreDomUtils.showErrorModalDefault(message, 'Error getting badge data.'); | ||||
|         } | ||||
|  | ||||
| @ -24,6 +24,9 @@ import { CoreNavigator } from '@services/navigator'; | ||||
| import { CoreListItemsManager } from '@classes/items-management/list-items-manager'; | ||||
| import { AddonBadgesUserBadgesSource } from '@addons/badges/classes/user-badges-source'; | ||||
| import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| import { CoreTime } from '@singletons/time'; | ||||
| import { Translate } from '@singletons'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays the list of calendar events. | ||||
| @ -39,6 +42,8 @@ export class AddonBadgesUserBadgesPage implements AfterViewInit, OnDestroy { | ||||
| 
 | ||||
|     @ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent; | ||||
| 
 | ||||
|     protected logView: () => void; | ||||
| 
 | ||||
|     constructor() { | ||||
|         let courseId = CoreNavigator.getRouteNumberParam('courseId') ?? 0; // Use 0 for site badges.
 | ||||
|         const userId = CoreNavigator.getRouteNumberParam('userId') ?? CoreSites.getCurrentSiteUserId(); | ||||
| @ -52,6 +57,16 @@ export class AddonBadgesUserBadgesPage implements AfterViewInit, OnDestroy { | ||||
|             CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(AddonBadgesUserBadgesSource, [courseId, userId]), | ||||
|             AddonBadgesUserBadgesPage, | ||||
|         ); | ||||
| 
 | ||||
|         this.logView = CoreTime.once(() => { | ||||
|             CoreAnalytics.logEvent({ | ||||
|                 type: CoreAnalyticsEventType.VIEW_ITEM_LIST, | ||||
|                 ws: 'core_badges_view_user_badges', | ||||
|                 name: Translate.instant('addon.badges.badges'), | ||||
|                 data: { courseId: this.badges.getSource().COURSE_ID, category: 'badges' }, | ||||
|                 url: '/badges/mybadges.php', | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -95,6 +110,8 @@ export class AddonBadgesUserBadgesPage implements AfterViewInit, OnDestroy { | ||||
| 
 | ||||
|         try { | ||||
|             await this.badges.reload(); | ||||
| 
 | ||||
|             this.logView(); | ||||
|         } catch (message) { | ||||
|             CoreDomUtils.showErrorModalDefault(message, 'Error loading badges'); | ||||
| 
 | ||||
|  | ||||
| @ -20,11 +20,14 @@ import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-lin | ||||
| import { CoreTag } from '@features/tag/services/tag'; | ||||
| import { CoreUser, CoreUserProfile } from '@features/user/services/user'; | ||||
| import { IonRefresher } from '@ionic/angular'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| import { CoreNavigator } from '@services/navigator'; | ||||
| import { CoreSites } from '@services/sites'; | ||||
| import { CoreDomUtils } from '@services/utils/dom'; | ||||
| import { CoreTextUtils } from '@services/utils/text'; | ||||
| import { CoreUrlUtils } from '@services/utils/url'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| import { CoreTime } from '@singletons/time'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays the list of blog entries. | ||||
| @ -43,7 +46,7 @@ export class AddonBlogEntriesPage implements OnInit { | ||||
|     protected canLoadMoreEntries = false; | ||||
|     protected canLoadMoreUserEntries = true; | ||||
|     protected siteHomeId: number; | ||||
|     protected fetchSuccess = false; | ||||
|     protected logView: () => void; | ||||
| 
 | ||||
|     loaded = false; | ||||
|     canLoadMore = false; | ||||
| @ -61,6 +64,25 @@ export class AddonBlogEntriesPage implements OnInit { | ||||
|     constructor() { | ||||
|         this.currentUserId = CoreSites.getCurrentSiteUserId(); | ||||
|         this.siteHomeId = CoreSites.getCurrentSiteHomeId(); | ||||
| 
 | ||||
|         this.logView = CoreTime.once(async () => { | ||||
|             await CoreUtils.ignoreErrors(AddonBlog.logView(this.filter)); | ||||
| 
 | ||||
|             CoreAnalytics.logEvent({ | ||||
|                 type: CoreAnalyticsEventType.VIEW_ITEM_LIST, | ||||
|                 ws: 'core_blog_view_entries', | ||||
|                 name: this.title, | ||||
|                 data: { | ||||
|                     ...this.filter, | ||||
|                     category: 'blog', | ||||
|                 }, | ||||
|                 url: CoreUrlUtils.addParamsToUrl('/blog/index.php', { | ||||
|                     ...this.filter, | ||||
|                     modid: this.filter.cmid, | ||||
|                     cmid: undefined, | ||||
|                 }), | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -200,10 +222,7 @@ export class AddonBlogEntriesPage implements OnInit { | ||||
| 
 | ||||
|             await Promise.all(promises); | ||||
| 
 | ||||
|             if (!this.fetchSuccess) { | ||||
|                 this.fetchSuccess = true; | ||||
|                 CoreUtils.ignoreErrors(AddonBlog.logView(this.filter)); | ||||
|             } | ||||
|             this.logView(); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'addon.blog.errorloadentries', true); | ||||
|             this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading.
 | ||||
|  | ||||
| @ -14,7 +14,6 @@ | ||||
| 
 | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; | ||||
| import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications'; | ||||
| import { CoreTagItem } from '@features/tag/services/tag'; | ||||
| import { CoreSites } from '@services/sites'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| @ -104,8 +103,6 @@ export class AddonBlogProvider { | ||||
|      * @returns Promise to be resolved when done. | ||||
|      */ | ||||
|     async logView(filter: AddonBlogFilter = {}, siteId?: string): Promise<CoreStatusWithWarningsWSResponse> { | ||||
|         CorePushNotifications.logViewListEvent('blog', 'core_blog_view_entries', filter, siteId); | ||||
| 
 | ||||
|         const site = await CoreSites.getSite(siteId); | ||||
| 
 | ||||
|         const data: AddonBlogViewEntriesWSParams = { | ||||
|  | ||||
| @ -49,6 +49,10 @@ import { | ||||
| } from '@classes/items-management/swipe-slides-dynamic-items-manager-source'; | ||||
| import { CoreSwipeSlidesDynamicItemsManager } from '@classes/items-management/swipe-slides-dynamic-items-manager'; | ||||
| import moment from 'moment-timezone'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| import { CoreUrlUtils } from '@services/utils/url'; | ||||
| import { CoreTime } from '@singletons/time'; | ||||
| import { Translate } from '@singletons'; | ||||
| 
 | ||||
| /** | ||||
|  * Component that displays a calendar. | ||||
| @ -81,6 +85,7 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro | ||||
|     // Observers and listeners.
 | ||||
|     protected undeleteEventObserver: CoreEventObserver; | ||||
|     protected managerUnsubscribe?: () => void; | ||||
|     protected logView: () => void; | ||||
| 
 | ||||
|     constructor(differs: KeyValueDiffers) { | ||||
|         this.currentSiteId = CoreSites.getCurrentSiteId(); | ||||
| @ -107,6 +112,29 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro | ||||
| 
 | ||||
|         this.hiddenDiffer = this.hidden; | ||||
|         this.filterDiffer = differs.find(this.filter ?? {}).create(); | ||||
| 
 | ||||
|         this.logView = CoreTime.once(() => { | ||||
|             const month = this.manager?.getSelectedItem(); | ||||
|             if (!month) { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             const params = { | ||||
|                 course: this.filter?.courseId, | ||||
|                 time: month.moment.unix(), | ||||
|             }; | ||||
| 
 | ||||
|             CoreAnalytics.logEvent({ | ||||
|                 type: CoreAnalyticsEventType.VIEW_ITEM_LIST, | ||||
|                 ws: 'core_calendar_get_calendar_monthly_view', | ||||
|                 name: Translate.instant('addon.calendar.detailedmonthviewtitle', { $a: this.periodName }), | ||||
|                 data: { | ||||
|                     ...params, | ||||
|                     category: 'calendar', | ||||
|                 }, | ||||
|                 url: CoreUrlUtils.addParamsToUrl('/calendar/view.php?view=month', params), | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @HostBinding('attr.hidden') get hiddenAttribute(): string | null { | ||||
| @ -124,7 +152,7 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro | ||||
|         const source = new AddonCalendarMonthSlidesItemsManagerSource(this, moment({ | ||||
|             year: this.initialYear, | ||||
|             month: this.initialMonth ? this.initialMonth - 1 : undefined, | ||||
|         })); | ||||
|         }).startOf('month')); | ||||
|         this.manager = new CoreSwipeSlidesDynamicItemsManager(source); | ||||
|         this.managerUnsubscribe = this.manager.addListener({ | ||||
|             onSelectedItemUpdated: (item) => { | ||||
| @ -176,6 +204,8 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro | ||||
|             await this.manager?.getSource().fetchData(); | ||||
| 
 | ||||
|             await this.manager?.getSource().load(this.manager?.getSelectedItem()); | ||||
| 
 | ||||
|             this.logView(); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true); | ||||
|         } | ||||
|  | ||||
| @ -25,6 +25,10 @@ import { AddonCalendarHelper, AddonCalendarFilter } from '../../services/calenda | ||||
| import { AddonCalendarOffline } from '../../services/calendar-offline'; | ||||
| import { CoreCategoryData, CoreCourses } from '@features/courses/services/courses'; | ||||
| import { CoreConstants } from '@/core/constants'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| import { CoreUrlUtils } from '@services/utils/url'; | ||||
| import { CoreTime } from '@singletons/time'; | ||||
| import { Translate } from '@singletons'; | ||||
| 
 | ||||
| /** | ||||
|  * Component that displays upcoming events. | ||||
| @ -54,6 +58,7 @@ export class AddonCalendarUpcomingEventsComponent implements OnInit, DoCheck, On | ||||
|     protected lookAhead = 0; | ||||
|     protected timeFormat?: string; | ||||
|     protected differ: KeyValueDiffer<unknown, unknown>; // To detect changes in the data input.
 | ||||
|     protected logView: () => void; | ||||
| 
 | ||||
|     // Observers.
 | ||||
|     protected undeleteEventObserver: CoreEventObserver; | ||||
| @ -84,6 +89,23 @@ export class AddonCalendarUpcomingEventsComponent implements OnInit, DoCheck, On | ||||
|         ); | ||||
| 
 | ||||
|         this.differ = differs.find([]).create(); | ||||
| 
 | ||||
|         this.logView = CoreTime.once(() => { | ||||
|             const params = { | ||||
|                 course: this.filter?.courseId, | ||||
|             }; | ||||
| 
 | ||||
|             CoreAnalytics.logEvent({ | ||||
|                 type: CoreAnalyticsEventType.VIEW_ITEM_LIST, | ||||
|                 ws: 'core_calendar_get_calendar_upcoming_view', | ||||
|                 name: Translate.instant('addon.calendar.upcomingevents'), | ||||
|                 data: { | ||||
|                     ...params, | ||||
|                     category: 'calendar', | ||||
|                 }, | ||||
|                 url: CoreUrlUtils.addParamsToUrl('/calendar/view.php?view=upcoming', params), | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -148,8 +170,9 @@ export class AddonCalendarUpcomingEventsComponent implements OnInit, DoCheck, On | ||||
|         try { | ||||
|             await Promise.all(promises); | ||||
| 
 | ||||
|             this.fetchEvents(); | ||||
|             await this.fetchEvents(); | ||||
| 
 | ||||
|             this.logView(); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true); | ||||
|         } | ||||
|  | ||||
| @ -11,10 +11,12 @@ | ||||
|     "currentmonth": "Current Month", | ||||
|     "daynext": "Next day", | ||||
|     "dayprev": "Previous day", | ||||
|     "dayviewtitle": "Day view: {{$a}}", | ||||
|     "defaultnotificationtime": "Default notification time", | ||||
|     "deleteallevents": "Delete all events", | ||||
|     "deleteevent": "Delete event", | ||||
|     "deleteoneevent": "Delete this event", | ||||
|     "detailedmonthviewtitle": "Detailed month view: {{$a}}", | ||||
|     "durationminutes": "Duration in minutes", | ||||
|     "durationnone": "Without duration", | ||||
|     "durationuntil": "Until", | ||||
|  | ||||
| @ -33,7 +33,7 @@ import { CoreCategoryData, CoreCourses, CoreEnrolledCourseData } from '@features | ||||
| import { CoreCoursesHelper } from '@features/courses/services/courses-helper'; | ||||
| import { AddonCalendarFilterComponent } from '../../components/filter/filter'; | ||||
| import moment from 'moment-timezone'; | ||||
| import { NgZone } from '@singletons'; | ||||
| import { NgZone, Translate } from '@singletons'; | ||||
| import { CoreNavigator } from '@services/navigator'; | ||||
| import { Params } from '@angular/router'; | ||||
| import { Subscription } from 'rxjs'; | ||||
| @ -47,6 +47,9 @@ import { | ||||
| } from '@classes/items-management/swipe-slides-dynamic-items-manager-source'; | ||||
| import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker'; | ||||
| import { AddonCalendarEventsSource } from '@addons/calendar/classes/events-source'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| import { CoreUrlUtils } from '@services/utils/url'; | ||||
| import { CoreTime } from '@singletons/time'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays the calendar events for a certain day. | ||||
| @ -73,6 +76,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { | ||||
|     protected onlineObserver: Subscription; | ||||
|     protected filterChangedObserver: CoreEventObserver; | ||||
|     protected managerUnsubscribe?: () => void; | ||||
|     protected logView: () => void; | ||||
| 
 | ||||
|     periodName?: string; | ||||
|     manager?: CoreSwipeSlidesDynamicItemsManager<PreloadedDay, AddonCalendarDaySlidesItemsManagerSource>; | ||||
| @ -186,6 +190,28 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { | ||||
|                 this.isOnline = CoreNetwork.isOnline(); | ||||
|             }); | ||||
|         }); | ||||
| 
 | ||||
|         this.logView = CoreTime.once(() => { | ||||
|             const day = this.manager?.getSelectedItem(); | ||||
|             if (!day) { | ||||
|                 return; | ||||
|             } | ||||
|             const params = { | ||||
|                 course: this.filter.courseId, | ||||
|                 time: day.moment.unix(), | ||||
|             }; | ||||
| 
 | ||||
|             CoreAnalytics.logEvent({ | ||||
|                 type: CoreAnalyticsEventType.VIEW_ITEM_LIST, | ||||
|                 ws: 'core_calendar_get_calendar_day_view', | ||||
|                 name: Translate.instant('addon.calendar.dayviewtitle', { $a: this.periodName }), | ||||
|                 data: { | ||||
|                     ...params, | ||||
|                     category: 'calendar', | ||||
|                 }, | ||||
|                 url: CoreUrlUtils.addParamsToUrl('/calendar/view.php?view=day', params), | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -209,7 +235,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { | ||||
|             year: CoreNavigator.getRouteNumberParam('year'), | ||||
|             month: month ? month - 1 : undefined, | ||||
|             date: CoreNavigator.getRouteNumberParam('day'), | ||||
|         })); | ||||
|         }).startOf('day')); | ||||
|         this.manager = new CoreSwipeSlidesDynamicItemsManager(source); | ||||
|         this.managerUnsubscribe = this.manager.addListener({ | ||||
|             onSelectedItemUpdated: (item) => { | ||||
| @ -246,6 +272,8 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { | ||||
|             await this.manager?.getSource().fetchData(this.filter.courseId); | ||||
| 
 | ||||
|             await this.manager?.getSource().load(this.manager?.getSelectedItem()); | ||||
| 
 | ||||
|             this.logView(); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true); | ||||
|         } | ||||
| @ -500,6 +528,7 @@ class AddonCalendarDaySlidesItemsManagerSource extends CoreSwipeSlidesDynamicIte | ||||
|     canCreate = false; | ||||
| 
 | ||||
|     protected dayPage: AddonCalendarDayPage; | ||||
|     protected sendLog = true; | ||||
| 
 | ||||
|     constructor(page: AddonCalendarDayPage, initialMoment: moment.Moment) { | ||||
|         super({ moment: initialMoment }); | ||||
| @ -780,6 +809,7 @@ class AddonCalendarDaySlidesItemsManagerSource extends CoreSwipeSlidesDynamicIte | ||||
|         promises.push(AddonCalendar.invalidateTimeFormat()); | ||||
| 
 | ||||
|         this.categories = undefined; // Get categories again.
 | ||||
|         this.sendLog = true; | ||||
| 
 | ||||
|         if (selectedDay) { | ||||
|             selectedDay.dirty = true; | ||||
|  | ||||
| @ -27,6 +27,9 @@ import { AddonCompetencyPlanCompetenciesSource } from '@addons/competency/classe | ||||
| import { AddonCompetencyCourseCompetenciesSource } from '@addons/competency/classes/competency-course-competencies-source'; | ||||
| import { CoreListItemsManager } from '@classes/items-management/list-items-manager'; | ||||
| import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| import { CoreSites } from '@services/sites'; | ||||
| import { CoreTime } from '@singletons/time'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays the list of competencies of a learning plan. | ||||
| @ -46,8 +49,11 @@ export class AddonCompetencyCompetenciesPage implements AfterViewInit, OnDestroy | ||||
| 
 | ||||
|     title = ''; | ||||
| 
 | ||||
|     protected logView: () => void; | ||||
| 
 | ||||
|     constructor() { | ||||
|         const planId = CoreNavigator.getRouteNumberParam('planId'); | ||||
|         this.logView = CoreTime.once(() => this.performLogView()); | ||||
| 
 | ||||
|         if (!planId) { | ||||
|             const courseId = CoreNavigator.getRequiredRouteNumberParam('courseId'); | ||||
| @ -96,6 +102,8 @@ export class AddonCompetencyCompetenciesPage implements AfterViewInit, OnDestroy | ||||
|             } else { | ||||
|                 this.title = Translate.instant('addon.competency.coursecompetencies'); | ||||
|             } | ||||
| 
 | ||||
|             this.logView(); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'Error getting competencies data.'); | ||||
|         } | ||||
| @ -122,4 +130,42 @@ export class AddonCompetencyCompetenciesPage implements AfterViewInit, OnDestroy | ||||
|         this.competencies.destroy(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Log view. | ||||
|      */ | ||||
|     protected performLogView(): void { | ||||
|         const source = this.competencies.getSource(); | ||||
| 
 | ||||
|         if (source instanceof AddonCompetencyPlanCompetenciesSource) { | ||||
|             CoreAnalytics.logEvent({ | ||||
|                 type: CoreAnalyticsEventType.VIEW_ITEM_LIST, | ||||
|                 ws: 'tool_lp_data_for_plan_page', | ||||
|                 name: this.title, | ||||
|                 data: { | ||||
|                     category: 'competency', | ||||
|                     planid: source.PLAN_ID, | ||||
|                 }, | ||||
|                 url: `/admin/tool/lp/plan.php?id=${source.PLAN_ID}`, | ||||
|             }); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if (source.USER_ID && source.USER_ID !== CoreSites.getCurrentSiteUserId()) { | ||||
|             // Only log event when viewing own competencies. In LMS viewing students competencies uses a different view.
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         CoreAnalytics.logEvent({ | ||||
|             type: CoreAnalyticsEventType.VIEW_ITEM_LIST, | ||||
|             ws: 'tool_lp_data_for_course_competencies_page', | ||||
|             name: this.title, | ||||
|             data: { | ||||
|                 category: 'competency', | ||||
|                 courseid: source.COURSE_ID, | ||||
|             }, | ||||
|             url: `/admin/tool/lp/coursecompetencies.php?courseid=${source.COURSE_ID}`, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -27,6 +27,7 @@ import { | ||||
|     AddonCompetency, | ||||
|     AddonCompetencyDataForPlanPageCompetency, | ||||
|     AddonCompetencyDataForCourseCompetenciesPageCompetency, | ||||
|     AddonCompetencyProvider, | ||||
| } from '@addons/competency/services/competency'; | ||||
| import { CoreNavigator } from '@services/navigator'; | ||||
| import { IonRefresher } from '@ionic/angular'; | ||||
| @ -38,6 +39,9 @@ import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/ | ||||
| import { AddonCompetencyPlanCompetenciesSource } from '@addons/competency/classes/competency-plan-competencies-source'; | ||||
| import { ActivatedRouteSnapshot } from '@angular/router'; | ||||
| import { AddonCompetencyCourseCompetenciesSource } from '@addons/competency/classes/competency-course-competencies-source'; | ||||
| import { CoreTime } from '@singletons/time'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| import { CoreUrlUtils } from '@services/utils/url'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays the competency information. | ||||
| @ -58,9 +62,11 @@ export class AddonCompetencyCompetencyPage implements OnInit, OnDestroy { | ||||
|     contextLevel?: string; | ||||
|     contextInstanceId?: number; | ||||
| 
 | ||||
|     protected fetchSuccess = false; | ||||
|     protected logView: () => void; | ||||
| 
 | ||||
|     constructor() { | ||||
|         this.logView = CoreTime.once(() => this.performLogView()); | ||||
| 
 | ||||
|         try { | ||||
|             const planId = CoreNavigator.getRouteNumberParam('planId'); | ||||
| 
 | ||||
| @ -156,31 +162,7 @@ export class AddonCompetencyCompetencyPage implements OnInit, OnDestroy { | ||||
|                 } | ||||
|             }); | ||||
| 
 | ||||
|             if (!this.fetchSuccess) { | ||||
|                 this.fetchSuccess = true; | ||||
|                 const name = this.competency.competency.competency.shortname; | ||||
| 
 | ||||
|                 if (source instanceof AddonCompetencyPlanCompetenciesSource) { | ||||
|                     this.planStatus && await CoreUtils.ignoreErrors( | ||||
|                         AddonCompetency.logCompetencyInPlanView( | ||||
|                             source.PLAN_ID, | ||||
|                             this.requireCompetencyId(), | ||||
|                             this.planStatus, | ||||
|                             name, | ||||
|                             source.user?.id, | ||||
|                         ), | ||||
|                     ); | ||||
|                 } else { | ||||
|                     await CoreUtils.ignoreErrors( | ||||
|                         AddonCompetency.logCompetencyInCourseView( | ||||
|                             source.COURSE_ID, | ||||
|                             this.requireCompetencyId(), | ||||
|                             name, | ||||
|                             source.USER_ID, | ||||
|                         ), | ||||
|                     ); | ||||
|                 } | ||||
|             } | ||||
|             this.logView(); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'Error getting competency data.'); | ||||
|         } | ||||
| @ -288,6 +270,73 @@ export class AddonCompetencyCompetencyPage implements OnInit, OnDestroy { | ||||
|         return competency.usercompetencysummary; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Log view. | ||||
|      */ | ||||
|     protected async performLogView(): Promise<void> { | ||||
|         if (!this.competency) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         const source = this.competencies.getSource(); | ||||
|         const compId = this.requireCompetencyId(); | ||||
|         const name = this.competency.competency.competency.shortname; | ||||
|         const userId = source.user?.id; | ||||
| 
 | ||||
|         if (source instanceof AddonCompetencyPlanCompetenciesSource) { | ||||
|             if (!this.planStatus) { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             await CoreUtils.ignoreErrors( | ||||
|                 AddonCompetency.logCompetencyInPlanView(source.PLAN_ID, compId, this.planStatus, name, userId), | ||||
|             ); | ||||
| 
 | ||||
|             const wsName = this.planStatus === AddonCompetencyProvider.STATUS_COMPLETE | ||||
|                 ? 'core_competency_user_competency_plan_viewed' | ||||
|                 : 'core_competency_user_competency_viewed_in_plan'; | ||||
| 
 | ||||
|             CoreAnalytics.logEvent({ | ||||
|                 type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|                 ws: wsName, | ||||
|                 name, | ||||
|                 data: { | ||||
|                     id: compId, | ||||
|                     category: 'competency', | ||||
|                     planid: source.PLAN_ID, | ||||
|                     planstatus: this.planStatus, | ||||
|                     userid: userId, | ||||
|                 }, | ||||
|                 url: CoreUrlUtils.addParamsToUrl('/admin/tool/lp/user_competency_in_plan.php', { | ||||
|                     planid: source.PLAN_ID, | ||||
|                     userid: userId, | ||||
|                     competencyid: compId, | ||||
|                 }), | ||||
|             }); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         await CoreUtils.ignoreErrors(AddonCompetency.logCompetencyInCourseView(source.COURSE_ID, compId, name, source.USER_ID)); | ||||
| 
 | ||||
|         CoreAnalytics.logEvent({ | ||||
|             type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|             ws: 'core_competency_user_competency_viewed_in_course', | ||||
|             name, | ||||
|             data: { | ||||
|                 id: compId, | ||||
|                 category: 'competency', | ||||
|                 courseid: source.COURSE_ID, | ||||
|                 userid: userId, | ||||
|             }, | ||||
|             url: CoreUrlUtils.addParamsToUrl('/admin/tool/lp/user_competency_in_course.php', { | ||||
|                 courseid: source.COURSE_ID, | ||||
|                 competencyid: compId, | ||||
|                 userid: userId, | ||||
|             }), | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  | ||||
| @ -20,6 +20,8 @@ import { CoreNavigator } from '@services/navigator'; | ||||
| import { CoreDomUtils } from '@services/utils/dom'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| import { ADDON_COMPETENCY_SUMMARY_PAGE } from '@addons/competency/competency.module'; | ||||
| import { CoreTime } from '@singletons/time'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays the competency summary. | ||||
| @ -36,7 +38,30 @@ export class AddonCompetencyCompetencySummaryPage implements OnInit { | ||||
|     contextLevel?: ContextLevel; | ||||
|     contextInstanceId?: number; | ||||
| 
 | ||||
|     protected fetchSuccess = false; // Whether a fetch was finished successfully.
 | ||||
|     protected logView: () => void; | ||||
| 
 | ||||
|     constructor() { | ||||
|         this.logView = CoreTime.once(async () => { | ||||
|             if (!this.competency) { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             await CoreUtils.ignoreErrors( | ||||
|                 AddonCompetency.logCompetencyView(this.competencyId, this.competency.competency.shortname), | ||||
|             ); | ||||
| 
 | ||||
|             CoreAnalytics.logEvent({ | ||||
|                 type: CoreAnalyticsEventType.VIEW_ITEM_LIST, | ||||
|                 ws: 'core_competency_competency_viewed', | ||||
|                 name: this.competency.competency.shortname, | ||||
|                 data: { | ||||
|                     competencyId: this.competencyId, | ||||
|                     category: 'competency', | ||||
|                 }, | ||||
|                 url: `/admin/tool/lp/user_competency.php?id=${this.competencyId}`, | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
| @ -77,10 +102,7 @@ export class AddonCompetencyCompetencySummaryPage implements OnInit { | ||||
| 
 | ||||
|             this.competency = result.competency; | ||||
| 
 | ||||
|             if (!this.fetchSuccess) { | ||||
|                 this.fetchSuccess = true; | ||||
|                 CoreUtils.ignoreErrors(AddonCompetency.logCompetencyView(this.competencyId, this.competency.competency.shortname)); | ||||
|             } | ||||
|             this.logView(); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'Error getting competency summary data.'); | ||||
|         } | ||||
|  | ||||
| @ -26,6 +26,10 @@ import { ADDON_COMPETENCY_SUMMARY_PAGE } from '@addons/competency/competency.mod | ||||
| import { CoreListItemsManager } from '@classes/items-management/list-items-manager'; | ||||
| import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker'; | ||||
| import { AddonCompetencyCourseCompetenciesSource } from '@addons/competency/classes/competency-course-competencies-source'; | ||||
| import { CoreTime } from '@singletons/time'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| import { CoreSites } from '@services/sites'; | ||||
| import { Translate } from '@singletons'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays the list of competencies of a course. | ||||
| @ -41,7 +45,11 @@ export class AddonCompetencyCourseCompetenciesPage implements OnInit, OnDestroy | ||||
|         AddonCompetencyCourseCompetenciesSource | ||||
|     >; | ||||
| 
 | ||||
|     protected logView: () => void; | ||||
| 
 | ||||
|     constructor() { | ||||
|         this.logView = CoreTime.once(() => this.performLogView()); | ||||
| 
 | ||||
|         try { | ||||
|             const courseId = CoreNavigator.getRequiredRouteNumberParam('courseId'); | ||||
|             const userId = CoreNavigator.getRouteNumberParam('userId'); | ||||
| @ -53,7 +61,6 @@ export class AddonCompetencyCourseCompetenciesPage implements OnInit, OnDestroy | ||||
|             this.competencies = new CoreListItemsManager(source, AddonCompetencyCourseCompetenciesPage); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModal(error); | ||||
| 
 | ||||
|             CoreNavigator.back(); | ||||
| 
 | ||||
|             return; | ||||
| @ -112,6 +119,8 @@ export class AddonCompetencyCourseCompetenciesPage implements OnInit, OnDestroy | ||||
|     protected async fetchCourseCompetencies(): Promise<void> { | ||||
|         try { | ||||
|             await this.competencies.getSource().reload(); | ||||
| 
 | ||||
|             this.logView(); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'Error getting course competencies data.'); | ||||
|         } | ||||
| @ -147,4 +156,26 @@ export class AddonCompetencyCourseCompetenciesPage implements OnInit, OnDestroy | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Log view. | ||||
|      */ | ||||
|     protected performLogView(): void { | ||||
|         const source = this.competencies.getSource(); | ||||
|         if (source.USER_ID && source.USER_ID !== CoreSites.getCurrentSiteUserId()) { | ||||
|             // Only log event when viewing own competencies. In LMS viewing students competencies uses a different view.
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         CoreAnalytics.logEvent({ | ||||
|             type: CoreAnalyticsEventType.VIEW_ITEM_LIST, | ||||
|             ws: 'tool_lp_data_for_course_competencies_page', | ||||
|             name: Translate.instant('addon.competency.coursecompetencies'), | ||||
|             data: { | ||||
|                 category: 'competency', | ||||
|                 courseid: source.COURSE_ID, | ||||
|             }, | ||||
|             url: `/admin/tool/lp/coursecompetencies.php?courseid=${source.COURSE_ID}`, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -23,6 +23,8 @@ import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/ | ||||
| import { AddonCompetencyPlansSource } from '@addons/competency/classes/competency-plans-source'; | ||||
| import { CoreListItemsManager } from '@classes/items-management/list-items-manager'; | ||||
| import { AddonCompetencyPlanCompetenciesSource } from '@addons/competency/classes/competency-plan-competencies-source'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| import { CoreTime } from '@singletons/time'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays a learning plan. | ||||
| @ -36,7 +38,11 @@ export class AddonCompetencyPlanPage implements OnInit, OnDestroy { | ||||
|     plans!: CoreSwipeNavigationItemsManager; | ||||
|     competencies!: CoreListItemsManager<AddonCompetencyDataForPlanPageCompetency, AddonCompetencyPlanCompetenciesSource>; | ||||
| 
 | ||||
|     protected logView: () => void; | ||||
| 
 | ||||
|     constructor() { | ||||
|         this.logView = CoreTime.once(() => this.performLogView()); | ||||
| 
 | ||||
|         try { | ||||
|             const planId = CoreNavigator.getRequiredRouteNumberParam('planId'); | ||||
|             const userId = CoreNavigator.getRouteNumberParam('userId'); | ||||
| @ -93,6 +99,8 @@ export class AddonCompetencyPlanPage implements OnInit, OnDestroy { | ||||
|     protected async fetchLearningPlan(): Promise<void> { | ||||
|         try { | ||||
|             await this.competencies.getSource().reload(); | ||||
| 
 | ||||
|             this.logView(); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'Error getting learning plan data.'); | ||||
|         } | ||||
| @ -111,4 +119,26 @@ export class AddonCompetencyPlanPage implements OnInit, OnDestroy { | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Log view. | ||||
|      */ | ||||
|     protected performLogView(): void { | ||||
|         if (!this.plan) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         const planId = this.competencies.getSource().PLAN_ID; | ||||
| 
 | ||||
|         CoreAnalytics.logEvent({ | ||||
|             type: CoreAnalyticsEventType.VIEW_ITEM_LIST, | ||||
|             ws: 'tool_lp_data_for_plan_page', | ||||
|             name: this.plan.plan.name, | ||||
|             data: { | ||||
|                 category: 'competency', | ||||
|                 planid: planId, | ||||
|             }, | ||||
|             url: `/admin/tool/lp/coursecompetencies.php?id=${planId}`, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -20,6 +20,10 @@ import { CoreNavigator } from '@services/navigator'; | ||||
| import { AddonCompetencyPlanFormatted, AddonCompetencyPlansSource } from '@addons/competency/classes/competency-plans-source'; | ||||
| import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker'; | ||||
| import { CoreListItemsManager } from '@classes/items-management/list-items-manager'; | ||||
| import { CoreTime } from '@singletons/time'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| import { CoreSites } from '@services/sites'; | ||||
| import { Translate } from '@singletons'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays the list of learning plans. | ||||
| @ -34,11 +38,25 @@ export class AddonCompetencyPlanListPage implements AfterViewInit, OnDestroy { | ||||
| 
 | ||||
|     plans: CoreListItemsManager<AddonCompetencyPlanFormatted, AddonCompetencyPlansSource>; | ||||
| 
 | ||||
|     protected logView: () => void; | ||||
| 
 | ||||
|     constructor() { | ||||
|         const userId = CoreNavigator.getRouteNumberParam('userId'); | ||||
|         const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(AddonCompetencyPlansSource, [userId]); | ||||
| 
 | ||||
|         this.plans = new CoreListItemsManager(source, AddonCompetencyPlanListPage); | ||||
| 
 | ||||
|         this.logView = CoreTime.once(async () => { | ||||
|             const userId = source.USER_ID ?? CoreSites.getCurrentSiteId(); | ||||
| 
 | ||||
|             CoreAnalytics.logEvent({ | ||||
|                 type: CoreAnalyticsEventType.VIEW_ITEM_LIST, | ||||
|                 ws: 'tool_lp_data_for_plans_page', | ||||
|                 name: Translate.instant('addon.competency.userplans'), | ||||
|                 data: { userid: userId }, | ||||
|                 url: `/admin/tool/lp/plans.php?userid=${userId}`, | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -58,6 +76,8 @@ export class AddonCompetencyPlanListPage implements AfterViewInit, OnDestroy { | ||||
|     protected async fetchLearningPlans(): Promise<void> { | ||||
|         try { | ||||
|             await this.plans.load(); | ||||
| 
 | ||||
|             this.logView(); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'Error getting learning plans data.'); | ||||
|         } | ||||
|  | ||||
| @ -16,7 +16,6 @@ import { Injectable } from '@angular/core'; | ||||
| import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; | ||||
| import { CoreCommentsArea } from '@features/comments/services/comments'; | ||||
| import { CoreCourseSummary, CoreCourseModuleSummary } from '@features/course/services/course'; | ||||
| import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications'; | ||||
| import { CoreUserSummary } from '@features/user/services/user'; | ||||
| import { CoreSites } from '@services/sites'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| @ -495,7 +494,7 @@ export class AddonCompetencyProvider { | ||||
|      * @param planId ID of the plan. | ||||
|      * @param competencyId ID of the competency. | ||||
|      * @param planStatus Current plan Status to decide what action should be logged. | ||||
|      * @param name Name of the competency. | ||||
|      * @param name Deprecated, not used anymore. | ||||
|      * @param userId User ID. If not defined, current user. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @returns Promise resolved when the WS call is successful. | ||||
| @ -525,12 +524,6 @@ export class AddonCompetencyProvider { | ||||
|             ? 'core_competency_user_competency_plan_viewed' | ||||
|             : 'core_competency_user_competency_viewed_in_plan'; | ||||
| 
 | ||||
|         CorePushNotifications.logViewEvent(competencyId, name, 'competency', wsName, { | ||||
|             planid: planId, | ||||
|             planstatus: planStatus, | ||||
|             userid: userId, | ||||
|         }, siteId); | ||||
| 
 | ||||
|         await site.write(wsName, params, preSets); | ||||
|     } | ||||
| 
 | ||||
| @ -539,7 +532,7 @@ export class AddonCompetencyProvider { | ||||
|      * | ||||
|      * @param courseId ID of the course. | ||||
|      * @param competencyId ID of the competency. | ||||
|      * @param name Name of the competency. | ||||
|      * @param name Deprecated, not used anymore. | ||||
|      * @param userId User ID. If not defined, current user. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @returns Promise resolved when the WS call is successful. | ||||
| @ -564,14 +557,7 @@ export class AddonCompetencyProvider { | ||||
|             typeExpected: 'boolean', | ||||
|         }; | ||||
| 
 | ||||
|         const wsName = 'core_competency_user_competency_viewed_in_course'; | ||||
| 
 | ||||
|         CorePushNotifications.logViewEvent(competencyId, name, 'competency', 'wsName', { | ||||
|             courseid: courseId, | ||||
|             userid: userId, | ||||
|         }, siteId); | ||||
| 
 | ||||
|         await site.write(wsName, params, preSets); | ||||
|         await site.write('core_competency_user_competency_viewed_in_course', params, preSets); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -593,10 +579,7 @@ export class AddonCompetencyProvider { | ||||
|             typeExpected: 'boolean', | ||||
|         }; | ||||
| 
 | ||||
|         const wsName = 'core_competency_competency_viewed'; | ||||
|         CorePushNotifications.logViewEvent(competencyId, name, 'competency', wsName, {}, siteId); | ||||
| 
 | ||||
|         await site.write(wsName, params, preSets); | ||||
|         await site.write('core_competency_competency_viewed', params, preSets); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -19,9 +19,12 @@ import { | ||||
| import { Component, OnInit } from '@angular/core'; | ||||
| import { CoreUser, CoreUserProfile } from '@features/user/services/user'; | ||||
| import { IonRefresher } from '@ionic/angular'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| import { CoreNavigator } from '@services/navigator'; | ||||
| import { CoreSites } from '@services/sites'; | ||||
| import { CoreDomUtils } from '@services/utils/dom'; | ||||
| import { Translate } from '@singletons'; | ||||
| import { CoreTime } from '@singletons/time'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays the course completion report. | ||||
| @ -33,6 +36,7 @@ import { CoreDomUtils } from '@services/utils/dom'; | ||||
| export class AddonCourseCompletionReportPage implements OnInit { | ||||
| 
 | ||||
|     protected userId!: number; | ||||
|     protected logView: () => void; | ||||
| 
 | ||||
|     courseId!: number; | ||||
|     completionLoaded = false; | ||||
| @ -42,6 +46,21 @@ export class AddonCourseCompletionReportPage implements OnInit { | ||||
|     statusText?: string; | ||||
|     user?: CoreUserProfile; | ||||
| 
 | ||||
|     constructor() { | ||||
|         this.logView = CoreTime.once(() => { | ||||
|             CoreAnalytics.logEvent({ | ||||
|                 type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|                 ws: 'core_completion_get_course_completion_status', | ||||
|                 name: Translate.instant('addon.coursecompletion.coursecompletion'), | ||||
|                 data: { | ||||
|                     course: this.courseId, | ||||
|                     user: this.userId, | ||||
|                 }, | ||||
|                 url: `/blocks/completionstatus/details.php?course=${this.courseId}&user=${this.userId}`, | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
| @ -77,6 +96,7 @@ export class AddonCourseCompletionReportPage implements OnInit { | ||||
|             this.showSelfComplete = AddonCourseCompletion.canMarkSelfCompleted(this.userId, this.completion); | ||||
| 
 | ||||
|             this.tracked = true; | ||||
|             this.logView(); | ||||
|         } catch (error) { | ||||
|             if (error && error.errorcode == 'notenroled') { | ||||
|                 // Not enrolled error, probably a teacher.
 | ||||
|  | ||||
| @ -57,7 +57,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo | ||||
|    @ViewChild(AddonModAssignSubmissionComponent) submissionComponent?: AddonModAssignSubmissionComponent; | ||||
| 
 | ||||
|     component = AddonModAssignProvider.COMPONENT; | ||||
|     moduleName = 'assign'; | ||||
|     pluginName = 'assign'; | ||||
| 
 | ||||
|     assign?: AddonModAssignAssign; // The assign object.
 | ||||
|     canViewAllSubmissions = false; // Whether the user can view all submissions.
 | ||||
| @ -230,14 +230,20 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo | ||||
|             return; // Shouldn't happen.
 | ||||
|         } | ||||
| 
 | ||||
|         await AddonModAssign.logView(this.assign.id, this.assign.name); | ||||
|         await CoreUtils.ignoreErrors(AddonModAssign.logView(this.assign.id)); | ||||
| 
 | ||||
|         this.analyticsLogEvent('mod_assign_view_assign'); | ||||
| 
 | ||||
|         if (this.canViewAllSubmissions) { | ||||
|             // User can see all submissions, log grading view.
 | ||||
|             CoreUtils.ignoreErrors(AddonModAssign.logGradingView(this.assign.id, this.assign.name)); | ||||
|             await CoreUtils.ignoreErrors(AddonModAssign.logGradingView(this.assign.id)); | ||||
| 
 | ||||
|             this.analyticsLogEvent('mod_assign_view_grading_table', { sendUrl: false }); | ||||
|         } else if (this.canViewOwnSubmission) { | ||||
|             // User can only see their own submission, log view the user submission.
 | ||||
|             CoreUtils.ignoreErrors(AddonModAssign.logSubmissionView(this.assign.id, this.assign.name)); | ||||
|             await CoreUtils.ignoreErrors(AddonModAssign.logSubmissionView(this.assign.id)); | ||||
| 
 | ||||
|             this.analyticsLogEvent('mod_assign_view_submission_status', { sendUrl: false }); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -45,6 +45,7 @@ | ||||
|     "gradelocked": "This grade is locked or overridden in the gradebook.", | ||||
|     "gradenotsynced": "Grade not synced", | ||||
|     "gradeoutof": "Grade out of {{$a}}", | ||||
|     "grading": "Grading", | ||||
|     "gradingstatus": "Grading status", | ||||
|     "groupsubmissionsettings": "Group submission settings", | ||||
|     "hiddenuser": "Participant", | ||||
| @ -101,6 +102,7 @@ | ||||
|     "submittedlate": "Assignment was submitted {{$a}} late", | ||||
|     "submittedovertime": "Assignment was submitted {{$a}} over the time limit", | ||||
|     "submittedundertime": "Assignment was submitted {{$a}} under the time limit", | ||||
|     "subpagetitle": "{{$a.contextname}} - {{$a.subpage}}", | ||||
|     "syncblockedusercomponent": "user grade", | ||||
|     "timelimit": "Time limit", | ||||
|     "timemodified": "Last modified", | ||||
|  | ||||
| @ -39,6 +39,7 @@ import { AddonModAssignOffline } from '../../services/assign-offline'; | ||||
| import { AddonModAssignSync } from '../../services/assign-sync'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| import { CoreWSExternalFile } from '@services/ws'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that allows adding or editing an assigment submission. | ||||
| @ -226,6 +227,17 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy, CanLeave { | ||||
|                 // No offline data found.
 | ||||
|                 this.hasOffline = false; | ||||
|             } | ||||
| 
 | ||||
|             CoreAnalytics.logEvent({ | ||||
|                 type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|                 ws: 'mod_assign_save_submission', | ||||
|                 name: Translate.instant('addon.mod_assign.subpagetitle', { | ||||
|                     contextname: this.assign.name, | ||||
|                     subpage: Translate.instant('addon.mod_assign.editsubmission'), | ||||
|                 }), | ||||
|                 data: { id: this.assign.id, category: 'assign' }, | ||||
|                 url: `/mod/assign/view.php?action=editsubmission&id=${this.moduleId}`, | ||||
|             }); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'Error getting assigment data.'); | ||||
| 
 | ||||
|  | ||||
| @ -34,6 +34,7 @@ import { | ||||
|     AddonModAssignManualSyncData, | ||||
|     AddonModAssignAutoSyncData, | ||||
| } from '../../services/assign-sync'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays a list of submissions of an assignment. | ||||
| @ -168,6 +169,21 @@ export class AddonModAssignSubmissionListPage implements AfterViewInit, OnDestro | ||||
|     protected async fetchAssignment(sync = false): Promise<void> { | ||||
|         try { | ||||
|             await this.submissions.getSource().loadAssignment(sync); | ||||
| 
 | ||||
|             if (!this.assign) { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             CoreAnalytics.logEvent({ | ||||
|                 type: CoreAnalyticsEventType.VIEW_ITEM_LIST, | ||||
|                 ws: 'mod_assign_get_submissions', | ||||
|                 name: Translate.instant('addon.mod_assign.subpagetitle', { | ||||
|                     contextname: this.assign.name, | ||||
|                     subpage: Translate.instant('addon.mod_assign.grading'), | ||||
|                 }), | ||||
|                 data: { assignid: this.assign.id, category: 'assign' }, | ||||
|                 url: `/mod/assign/view.php?id=${this.assign.cmid}&action=grading`, | ||||
|             }); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'Error getting assigment data.'); | ||||
|         } | ||||
|  | ||||
| @ -25,6 +25,9 @@ import { CoreDomUtils } from '@services/utils/dom'; | ||||
| import { AddonModAssignListFilterName, AddonModAssignSubmissionsSource } from '../../classes/submissions-source'; | ||||
| import { AddonModAssignSubmissionComponent } from '../../components/submission/submission'; | ||||
| import { AddonModAssign, AddonModAssignAssign } from '../../services/assign'; | ||||
| import { CoreTime } from '@singletons/time'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| import { Translate } from '@singletons'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays a submission. | ||||
| @ -49,8 +52,29 @@ export class AddonModAssignSubmissionReviewPage implements OnInit, OnDestroy, Ca | ||||
|     protected assign?: AddonModAssignAssign; // The assignment the submission belongs to.
 | ||||
|     protected blindMarking = false; // Whether it uses blind marking.
 | ||||
|     protected forceLeave = false; // To allow leaving the page without checking for changes.
 | ||||
|     protected logView: () => void; | ||||
| 
 | ||||
|     constructor(protected route: ActivatedRoute) { } | ||||
|     constructor(protected route: ActivatedRoute) { | ||||
|         this.logView = CoreTime.once(() => { | ||||
|             if (!this.assign) { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             const id = this.blindMarking ? this.blindId : this.submitId; | ||||
|             const paramName = this.blindMarking ? 'blindid' : 'userid'; | ||||
| 
 | ||||
|             CoreAnalytics.logEvent({ | ||||
|                 type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|                 ws: 'mod_assign_get_submission_status', | ||||
|                 name: Translate.instant('addon.mod_assign.subpagetitle', { | ||||
|                     contextname: this.assign.name, | ||||
|                     subpage: Translate.instant('addon.mod_assign.grading'), | ||||
|                 }), | ||||
|                 data: { id, assignid: this.assign.id, category: 'assign' }, | ||||
|                 url: `/mod/assign/view.php?id=${this.assign.cmid}&action=grader&${paramName}=${id}`, | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
| @ -84,6 +108,7 @@ export class AddonModAssignSubmissionReviewPage implements OnInit, OnDestroy, Ca | ||||
|             } | ||||
| 
 | ||||
|             this.fetchSubmission().finally(() => { | ||||
|                 this.logView(); | ||||
|                 this.loaded = true; | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
| @ -878,23 +878,19 @@ export class AddonModAssignProvider { | ||||
|      * Report an assignment submission as being viewed. | ||||
|      * | ||||
|      * @param assignid Assignment ID. | ||||
|      * @param name Name of the assign. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @returns Promise resolved when the WS call is successful. | ||||
|      */ | ||||
|     async logSubmissionView(assignid: number, name?: string, siteId?: string): Promise<void> { | ||||
|     async logSubmissionView(assignid: number, siteId?: string): Promise<void> { | ||||
|         const params: AddonModAssignViewSubmissionStatusWSParams = { | ||||
|             assignid, | ||||
|         }; | ||||
| 
 | ||||
|         await CoreCourseLogHelper.logSingle( | ||||
|         await CoreCourseLogHelper.log( | ||||
|             'mod_assign_view_submission_status', | ||||
|             params, | ||||
|             AddonModAssignProvider.COMPONENT, | ||||
|             assignid, | ||||
|             name, | ||||
|             'assign', | ||||
|             {}, | ||||
|             siteId, | ||||
|         ); | ||||
|     } | ||||
| @ -903,23 +899,19 @@ export class AddonModAssignProvider { | ||||
|      * Report an assignment grading table is being viewed. | ||||
|      * | ||||
|      * @param assignid Assignment ID. | ||||
|      * @param name Name of the assign. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @returns Promise resolved when the WS call is successful. | ||||
|      */ | ||||
|     async logGradingView(assignid: number, name?: string, siteId?: string): Promise<void> { | ||||
|     async logGradingView(assignid: number, siteId?: string): Promise<void> { | ||||
|         const params: AddonModAssignViewGradingTableWSParams = { | ||||
|             assignid, | ||||
|         }; | ||||
| 
 | ||||
|         await CoreCourseLogHelper.logSingle( | ||||
|         await CoreCourseLogHelper.log( | ||||
|             'mod_assign_view_grading_table', | ||||
|             params, | ||||
|             AddonModAssignProvider.COMPONENT, | ||||
|             assignid, | ||||
|             name, | ||||
|             'assign', | ||||
|             {}, | ||||
|             siteId, | ||||
|         ); | ||||
|     } | ||||
| @ -928,23 +920,19 @@ export class AddonModAssignProvider { | ||||
|      * Report an assign as being viewed. | ||||
|      * | ||||
|      * @param assignid Assignment ID. | ||||
|      * @param name Name of the assign. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @returns Promise resolved when the WS call is successful. | ||||
|      */ | ||||
|     async logView(assignid: number, name?: string, siteId?: string): Promise<void> { | ||||
|     async logView(assignid: number, siteId?: string): Promise<void> { | ||||
|         const params: AddonModAssignViewAssignWSParams = { | ||||
|             assignid, | ||||
|         }; | ||||
| 
 | ||||
|         await CoreCourseLogHelper.logSingle( | ||||
|         await CoreCourseLogHelper.log( | ||||
|             'mod_assign_view_assign', | ||||
|             params, | ||||
|             AddonModAssignProvider.COMPONENT, | ||||
|             assignid, | ||||
|             name, | ||||
|             'assign', | ||||
|             {}, | ||||
|             siteId, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
| @ -44,7 +44,7 @@ import { | ||||
| export class AddonModBBBIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit { | ||||
| 
 | ||||
|     component = AddonModBBBService.COMPONENT; | ||||
|     moduleName = 'bigbluebuttonbn'; | ||||
|     pluginName = 'bigbluebuttonbn'; | ||||
|     bbb?: AddonModBBBData; | ||||
|     groupInfo?: CoreGroupInfo; | ||||
|     groupId = 0; | ||||
| @ -226,7 +226,9 @@ export class AddonModBBBIndexComponent extends CoreCourseModuleMainActivityCompo | ||||
|             return; // Shouldn't happen.
 | ||||
|         } | ||||
| 
 | ||||
|         await AddonModBBB.logView(this.bbb.id, this.bbb.name); | ||||
|         await CoreUtils.ignoreErrors(AddonModBBB.logView(this.bbb.id)); | ||||
| 
 | ||||
|         this.analyticsLogEvent('mod_bigbluebuttonbn_view_bigbluebuttonbn'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -276,23 +276,19 @@ export class AddonModBBBService { | ||||
|      * Report a BBB as being viewed. | ||||
|      * | ||||
|      * @param id BBB instance ID. | ||||
|      * @param name Name of the BBB. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @returns Promise resolved when the WS call is successful. | ||||
|      */ | ||||
|     async logView(id: number, name?: string, siteId?: string): Promise<void> { | ||||
|     async logView(id: number, siteId?: string): Promise<void> { | ||||
|         const params: AddonModBBBViewBigBlueButtonBNWSParams = { | ||||
|             bigbluebuttonbnid: id, | ||||
|         }; | ||||
| 
 | ||||
|         await CoreCourseLogHelper.logSingle( | ||||
|         await CoreCourseLogHelper.log( | ||||
|             'mod_bigbluebuttonbn_view_bigbluebuttonbn', | ||||
|             params, | ||||
|             AddonModBBBService.COMPONENT, | ||||
|             id, | ||||
|             name, | ||||
|             'bigbluebuttonbn', | ||||
|             {}, | ||||
|             siteId, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
| @ -19,6 +19,7 @@ import { CoreCourseContentsPage } from '@features/course/pages/contents/contents | ||||
| import { CoreCourse } from '@features/course/services/course'; | ||||
| import { CoreNavigator } from '@services/navigator'; | ||||
| import { AddonModBookModuleHandlerService } from '../../services/handlers/module'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| 
 | ||||
| /** | ||||
|  * Component that displays a book entry page. | ||||
| @ -29,6 +30,7 @@ import { AddonModBookModuleHandlerService } from '../../services/handlers/module | ||||
| }) | ||||
| export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy { | ||||
| 
 | ||||
|     pluginName = 'book'; | ||||
|     showNumbers = true; | ||||
|     addPadding = true; | ||||
|     showBullets = false; | ||||
| @ -102,7 +104,9 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     protected async logActivity(): Promise<void> { | ||||
|         AddonModBook.logView(this.module.instance, undefined, this.module.name); | ||||
|         await CoreUtils.ignoreErrors(AddonModBook.logView(this.module.instance)); | ||||
| 
 | ||||
|         this.analyticsLogEvent('mod_book_view_book'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -40,6 +40,8 @@ import { | ||||
|     AddonModBookProvider, | ||||
|     AddonModBookTocChapter, | ||||
| } from '../../services/book'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| import { CoreUrlUtils } from '@services/utils/url'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays a book contents. | ||||
| @ -286,7 +288,15 @@ export class AddonModBookContentsPage implements OnInit, OnDestroy { | ||||
|         } | ||||
| 
 | ||||
|         // Chapter loaded, log view.
 | ||||
|         await CoreUtils.ignoreErrors(AddonModBook.logView(this.module.instance, chapterId, this.module.name)); | ||||
|         await CoreUtils.ignoreErrors(AddonModBook.logView(this.module.instance, chapterId)); | ||||
| 
 | ||||
|         CoreAnalytics.logEvent({ | ||||
|             type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|             ws: 'mod_book_view_book', | ||||
|             name: this.module.name, | ||||
|             data: { id: this.module.instance, category: 'book', chapterid: chapterId }, | ||||
|             url: CoreUrlUtils.addParamsToUrl(`/mod/book/view.php?id=${this.module.id}`, { chapterid: chapterId }), | ||||
|         }); | ||||
| 
 | ||||
|         const currentChapterIndex = this.chapters.findIndex((chapter) => chapter.id == chapterId); | ||||
|         const isLastChapter = currentChapterIndex < 0 || this.chapters[currentChapterIndex + 1] === undefined; | ||||
|  | ||||
| @ -359,24 +359,20 @@ export class AddonModBookProvider { | ||||
|      * | ||||
|      * @param id Module ID. | ||||
|      * @param chapterId Chapter ID. | ||||
|      * @param name Name of the book. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @returns Promise resolved when the WS call is successful. | ||||
|      */ | ||||
|     async logView(id: number, chapterId?: number, name?: string, siteId?: string): Promise<void> { | ||||
|     async logView(id: number, chapterId?: number, siteId?: string): Promise<void> { | ||||
|         const params: AddonModBookViewBookWSParams = { | ||||
|             bookid: id, | ||||
|             chapterid: chapterId, | ||||
|         }; | ||||
| 
 | ||||
|         await CoreCourseLogHelper.logSingle( | ||||
|         await CoreCourseLogHelper.log( | ||||
|             'mod_book_view_book', | ||||
|             params, | ||||
|             AddonModBookProvider.COMPONENT, | ||||
|             id, | ||||
|             name, | ||||
|             'book', | ||||
|             { chapterid: chapterId }, | ||||
|             siteId, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
| @ -32,7 +32,7 @@ import { AddonModChatModuleHandlerService } from '../../services/handlers/module | ||||
| export class AddonModChatIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit { | ||||
| 
 | ||||
|     component = AddonModChatProvider.COMPONENT; | ||||
|     moduleName = 'chat'; | ||||
|     pluginName = 'chat'; | ||||
|     chat?: AddonModChatChat; | ||||
|     chatInfo?: { | ||||
|         date: string; | ||||
| @ -85,7 +85,9 @@ export class AddonModChatIndexComponent extends CoreCourseModuleMainActivityComp | ||||
|             return; // Shouldn't happen.
 | ||||
|         } | ||||
| 
 | ||||
|         await AddonModChat.logView(this.chat.id, this.chat.name); | ||||
|         await AddonModChat.logView(this.chat.id); | ||||
| 
 | ||||
|         this.analyticsLogEvent('mod_chat_view_chat'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -28,6 +28,8 @@ import { Subscription } from 'rxjs'; | ||||
| import { AddonModChatUsersModalComponent, AddonModChatUsersModalResult } from '../../components/users-modal/users-modal'; | ||||
| import { AddonModChat, AddonModChatProvider, AddonModChatUser } from '../../services/chat'; | ||||
| import { AddonModChatFormattedMessage, AddonModChatHelper } from '../../services/chat-helper'; | ||||
| import { CoreTime } from '@singletons/time'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays a chat session. | ||||
| @ -61,6 +63,7 @@ export class AddonModChatChatPage implements OnInit, OnDestroy, CanLeave { | ||||
|     protected viewDestroyed = false; | ||||
|     protected pollingRunning = false; | ||||
|     protected users: AddonModChatUser[] = []; | ||||
|     protected logView: () => void; | ||||
| 
 | ||||
|     constructor() { | ||||
|         this.currentUserId = CoreSites.getCurrentSiteUserId(); | ||||
| @ -71,6 +74,16 @@ export class AddonModChatChatPage implements OnInit, OnDestroy, CanLeave { | ||||
|                 this.isOnline = CoreNetwork.isOnline(); | ||||
|             }); | ||||
|         }); | ||||
| 
 | ||||
|         this.logView = CoreTime.once(() => { | ||||
|             CoreAnalytics.logEvent({ | ||||
|                 type: CoreAnalyticsEventType.VIEW_ITEM_LIST, | ||||
|                 ws: 'mod_chat_get_chat_latest_messages', | ||||
|                 name: this.title, | ||||
|                 data: { chatid: this.chatId, category: 'chat' }, | ||||
|                 url: `/mod/chat/gui_ajax/index.php?id=${this.chatId}`, | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -88,6 +101,7 @@ export class AddonModChatChatPage implements OnInit, OnDestroy, CanLeave { | ||||
|             await this.fetchMessages(); | ||||
| 
 | ||||
|             this.startPolling(); | ||||
|             this.logView(); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'addon.mod_chat.errorwhileconnecting', true); | ||||
|             CoreNavigator.back(); | ||||
|  | ||||
| @ -21,6 +21,9 @@ import { CoreDomUtils } from '@services/utils/dom'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| import { AddonModChat } from '../../services/chat'; | ||||
| import { AddonModChatFormattedSessionMessage, AddonModChatHelper } from '../../services/chat-helper'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| import { Translate } from '@singletons'; | ||||
| import { CoreTime } from '@singletons/time'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays list of chat session messages. | ||||
| @ -42,6 +45,19 @@ export class AddonModChatSessionMessagesPage implements OnInit { | ||||
|     protected sessionStart!: number; | ||||
|     protected sessionEnd!: number; | ||||
|     protected groupId!: number; | ||||
|     protected logView: () => void; | ||||
| 
 | ||||
|     constructor() { | ||||
|         this.logView = CoreTime.once(() => { | ||||
|             CoreAnalytics.logEvent({ | ||||
|                 type: CoreAnalyticsEventType.VIEW_ITEM_LIST, | ||||
|                 ws: 'mod_chat_view_sessions', | ||||
|                 name: Translate.instant('addon.mod_chat.messages'), | ||||
|                 data: { chatid: this.chatId, category: 'chat' }, | ||||
|                 url: `/mod/chat/report.php?id=${this.cmId}&start=${this.sessionStart}&end=${this.sessionEnd}`, | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|  | ||||
| @ -12,7 +12,7 @@ | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { AfterViewInit, Component, OnDestroy, ViewChild } from '@angular/core'; | ||||
| import { AfterViewInit, Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; | ||||
| import { CoreListItemsManager } from '@classes/items-management/list-items-manager'; | ||||
| import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker'; | ||||
| import { CoreSplitViewComponent } from '@components/split-view/split-view'; | ||||
| @ -21,6 +21,9 @@ import { CoreGroupInfo } from '@services/groups'; | ||||
| import { CoreNavigator } from '@services/navigator'; | ||||
| import { CoreDomUtils } from '@services/utils/dom'; | ||||
| import { AddonModChatSessionFormatted, AddonModChatSessionsSource } from '../../classes/chat-sessions-source'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| import { CoreTime } from '@singletons/time'; | ||||
| import { Translate } from '@singletons'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays list of chat sessions. | ||||
| @ -29,14 +32,32 @@ import { AddonModChatSessionFormatted, AddonModChatSessionsSource } from '../../ | ||||
|     selector: 'page-addon-mod-chat-sessions', | ||||
|     templateUrl: 'sessions.html', | ||||
| }) | ||||
| export class AddonModChatSessionsPage implements AfterViewInit, OnDestroy { | ||||
| export class AddonModChatSessionsPage implements OnInit, AfterViewInit, OnDestroy { | ||||
| 
 | ||||
|     @ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent; | ||||
| 
 | ||||
|     sessions!: CoreListItemsManager<AddonModChatSessionFormatted, AddonModChatSessionsSource>; | ||||
|     courseId?: number; | ||||
|     protected logView: () => void; | ||||
| 
 | ||||
|     constructor() { | ||||
|         this.logView = CoreTime.once(() => { | ||||
|             const source = this.sessions.getSource(); | ||||
| 
 | ||||
|             CoreAnalytics.logEvent({ | ||||
|                 type: CoreAnalyticsEventType.VIEW_ITEM_LIST, | ||||
|                 ws: 'mod_chat_view_sessions', | ||||
|                 name: Translate.instant('addon.mod_chat.chatreport'), | ||||
|                 data: { chatid: source.CHAT_ID, category: 'chat' }, | ||||
|                 url: `/mod/chat/report.php?id=${source.CM_ID}`, | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     ngOnInit(): void { | ||||
|         try { | ||||
|             this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId'); | ||||
|             const chatId = CoreNavigator.getRequiredRouteNumberParam('chatId'); | ||||
| @ -91,6 +112,8 @@ export class AddonModChatSessionsPage implements AfterViewInit, OnDestroy { | ||||
|     async fetchSessions(): Promise<void> { | ||||
|         try { | ||||
|             await this.sessions.load(); | ||||
| 
 | ||||
|             this.logView(); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'core.errorloadingcontent', true); | ||||
|         } | ||||
|  | ||||
| @ -87,23 +87,19 @@ export class AddonModChatProvider { | ||||
|      * Report a chat as being viewed. | ||||
|      * | ||||
|      * @param id Chat instance ID. | ||||
|      * @param name Name of the chat. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @returns Promise resolved when the WS call is successful. | ||||
|      */ | ||||
|     async logView(id: number, name?: string, siteId?: string): Promise<void> { | ||||
|     async logView(id: number, siteId?: string): Promise<void> { | ||||
|         const params: AddonModChatViewChatWSParams = { | ||||
|             chatid: id, | ||||
|         }; | ||||
| 
 | ||||
|         await CoreCourseLogHelper.logSingle( | ||||
|         await CoreCourseLogHelper.log( | ||||
|             'mod_chat_view_chat', | ||||
|             params, | ||||
|             AddonModChatProvider.COMPONENT, | ||||
|             id, | ||||
|             name, | ||||
|             'chat', | ||||
|             {}, | ||||
|             siteId, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
| @ -48,7 +48,7 @@ import { AddonModChoicePrefetchHandler } from '../../services/handlers/prefetch' | ||||
| export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit { | ||||
| 
 | ||||
|     component = AddonModChoiceProvider.COMPONENT; | ||||
|     moduleName = 'choice'; | ||||
|     pluginName = 'choice'; | ||||
| 
 | ||||
|     choice?: AddonModChoiceChoice; | ||||
|     options: AddonModChoiceOption[] = []; | ||||
| @ -321,7 +321,9 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo | ||||
|             return; // Shouldn't happen.
 | ||||
|         } | ||||
| 
 | ||||
|         await AddonModChoice.logView(this.choice.id, this.choice.name); | ||||
|         await AddonModChoice.logView(this.choice.id); | ||||
| 
 | ||||
|         this.analyticsLogEvent('mod_choice_view_choice'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -386,6 +388,8 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo | ||||
|                 this.checkCompletion(); | ||||
|             } | ||||
| 
 | ||||
|             this.analyticsLogEvent('mod_choice_view_choice', { data: { notify: 'choicesaved' } }); | ||||
| 
 | ||||
|             await this.dataUpdated(online); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'addon.mod_choice.cannotsubmit', true); | ||||
| @ -412,6 +416,8 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo | ||||
| 
 | ||||
|             this.content?.scrollToTop(); | ||||
| 
 | ||||
|             this.analyticsLogEvent('mod_choice_view_choice', { data: { action: 'delchoice' } }); | ||||
| 
 | ||||
|             // Refresh the data. Don't call dataUpdated because deleting an answer doesn't mark the choice as outdated.
 | ||||
|             await this.refreshContent(false); | ||||
|         } catch (error) { | ||||
|  | ||||
| @ -365,23 +365,19 @@ export class AddonModChoiceProvider { | ||||
|      * Report the choice as being viewed. | ||||
|      * | ||||
|      * @param id Choice ID. | ||||
|      * @param name Name of the choice. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @returns Promise resolved when the WS call is successful. | ||||
|      */ | ||||
|     logView(id: number, name?: string, siteId?: string): Promise<void> { | ||||
|     logView(id: number, siteId?: string): Promise<void> { | ||||
|         const params: AddonModChoiceViewChoiceWSParams = { | ||||
|             choiceid: id, | ||||
|         }; | ||||
| 
 | ||||
|         return CoreCourseLogHelper.logSingle( | ||||
|         return CoreCourseLogHelper.log( | ||||
|             'mod_choice_view_choice', | ||||
|             params, | ||||
|             AddonModChoiceProvider.COMPONENT, | ||||
|             id, | ||||
|             name, | ||||
|             'choice', | ||||
|             {}, | ||||
|             siteId, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
| @ -45,6 +45,8 @@ import { AddonModDataModuleHandlerService } from '../../services/handlers/module | ||||
| import { AddonModDataPrefetchHandler } from '../../services/handlers/prefetch'; | ||||
| import { AddonModDataComponentsCompileModule } from '../components-compile.module'; | ||||
| import { AddonModDataSearchComponent } from '../search/search'; | ||||
| import { CoreUrlUtils } from '@services/utils/url'; | ||||
| import { CoreTime } from '@singletons/time'; | ||||
| 
 | ||||
| const contentToken = '<!-- CORE-DATABASE-CONTENT-GOES-HERE -->'; | ||||
| 
 | ||||
| @ -59,7 +61,7 @@ const contentToken = '<!-- CORE-DATABASE-CONTENT-GOES-HERE -->'; | ||||
| export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit, OnDestroy { | ||||
| 
 | ||||
|     component = AddonModDataProvider.COMPONENT; | ||||
|     moduleName = 'data'; | ||||
|     pluginName = 'data'; | ||||
| 
 | ||||
|     access?: AddonModDataGetDataAccessInformationWSResponse; | ||||
|     database?: AddonModDataData; | ||||
| @ -114,6 +116,7 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp | ||||
|     protected entryChangedObserver?: CoreEventObserver; | ||||
|     protected ratingOfflineObserver?: CoreEventObserver; | ||||
|     protected ratingSyncObserver?: CoreEventObserver; | ||||
|     protected logSearch?: () => void; | ||||
| 
 | ||||
|     constructor( | ||||
|         protected content?: IonContent, | ||||
| @ -404,6 +407,7 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp | ||||
|         // Add data to search object.
 | ||||
|         if (modalData) { | ||||
|             this.search = modalData; | ||||
|             this.logSearch = CoreTime.once(() => this.performLogSearch()); | ||||
|             this.searchEntries(0); | ||||
|         } | ||||
|     } | ||||
| @ -420,8 +424,8 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp | ||||
| 
 | ||||
|         try { | ||||
|             await this.fetchEntriesData(); | ||||
|             // Log activity view for coherence with Moodle web.
 | ||||
|             await this.logActivity(); | ||||
| 
 | ||||
|             this.logSearch?.(); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true); | ||||
|         } finally { | ||||
| @ -470,9 +474,6 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp | ||||
| 
 | ||||
|         try { | ||||
|             await this.fetchEntriesData(); | ||||
| 
 | ||||
|             // Log activity view for coherence with Moodle web.
 | ||||
|             return this.logActivity(); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true); | ||||
|         } | ||||
| @ -535,7 +536,34 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         await AddonModData.logView(this.database.id, this.database.name); | ||||
|         await AddonModData.logView(this.database.id); | ||||
| 
 | ||||
|         this.analyticsLogEvent('mod_data_view_database'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Log search. | ||||
|      */ | ||||
|     protected async performLogSearch(): Promise<void> { | ||||
|         if (!this.database || !this.search.searching) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         const params: Record<string, unknown> = { | ||||
|             perpage: AddonModDataProvider.PER_PAGE, | ||||
|             search: !this.search.searchingAdvanced ? this.search.text : '', | ||||
|             sort: this.search.sortBy, | ||||
|             order: this.search.sortDirection, | ||||
|             advanced: this.search.searchingAdvanced ? 1 : 0, | ||||
|             filter: 1, | ||||
|         }; | ||||
| 
 | ||||
|         // @todo: Add advanced search parameters. Leave them empty if not using advanced search.
 | ||||
| 
 | ||||
|         this.analyticsLogEvent('mod_data_search_entries', { | ||||
|             data: params, | ||||
|             url: CoreUrlUtils.addParamsToUrl(`/mod/data/view.php?d=${this.database.id}`, params), | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -43,6 +43,8 @@ import { AddonModDataHelper } from '../../services/data-helper'; | ||||
| import { CoreDom } from '@singletons/dom'; | ||||
| import { AddonModDataEntryFieldInitialized } from '../../classes/base-field-plugin-component'; | ||||
| import { CoreTextUtils } from '@services/utils/text'; | ||||
| import { CoreTime } from '@singletons/time'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays the view edit page. | ||||
| @ -65,6 +67,7 @@ export class AddonModDataEditPage implements OnInit { | ||||
|     protected initialSelectedGroup?: number; | ||||
|     protected isEditing = false; | ||||
|     protected originalData: AddonModDataEntryFields = {}; | ||||
|     protected logView: () => void; | ||||
| 
 | ||||
|     entry?: AddonModDataEntry; | ||||
|     fields: Record<number, AddonModDataField> = {}; | ||||
| @ -94,6 +97,20 @@ export class AddonModDataEditPage implements OnInit { | ||||
|     constructor() { | ||||
|         this.siteId = CoreSites.getCurrentSiteId(); | ||||
|         this.editForm = new FormGroup({}); | ||||
| 
 | ||||
|         this.logView = CoreTime.once(() => { | ||||
|             if (!this.database) { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             CoreAnalytics.logEvent({ | ||||
|                 type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|                 ws: this.isEditing ? 'mod_data_update_entry' : 'mod_data_add_entry', | ||||
|                 name: this.title, | ||||
|                 data: { databaseid: this.database.id, category: 'data' }, | ||||
|                 url: '/mod/data/edit.php?' + (this.isEditing ? `d=${this.database.id}&rid=${this.entryId}` : `id=${this.moduleId}`), | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -230,6 +247,7 @@ export class AddonModDataEditPage implements OnInit { | ||||
|             } | ||||
| 
 | ||||
|             this.editFormRender = this.displayEditFields(); | ||||
|             this.logView(); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true); | ||||
|         } | ||||
|  | ||||
| @ -36,6 +36,8 @@ import { AddonModDataProvider, | ||||
| } from '../../services/data'; | ||||
| import { AddonModDataHelper } from '../../services/data-helper'; | ||||
| import { AddonModDataSyncProvider } from '../../services/data-sync'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| import { CoreTime } from '@singletons/time'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays the view entry page. | ||||
| @ -55,9 +57,9 @@ export class AddonModDataEntryPage implements OnInit, OnDestroy { | ||||
|     protected entryChangedObserver: CoreEventObserver; // It will observe the changed entry event.
 | ||||
|     protected fields: Record<number, AddonModDataField> = {}; | ||||
|     protected fieldsArray: AddonModDataField[] = []; | ||||
|     protected logAfterFetch = true; | ||||
|     protected sortBy = 0; | ||||
|     protected sortDirection = 'DESC'; | ||||
|     protected logView: () => void; | ||||
| 
 | ||||
|     moduleId = 0; | ||||
|     courseId!: number; | ||||
| @ -129,6 +131,8 @@ export class AddonModDataEntryPage implements OnInit, OnDestroy { | ||||
|                 } | ||||
|             } | ||||
|         }, this.siteId); | ||||
| 
 | ||||
|         this.logView = CoreTime.once(() => this.performLogView()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -219,13 +223,7 @@ export class AddonModDataEntryPage implements OnInit, OnDestroy { | ||||
|                 access: this.access, | ||||
|             }; | ||||
| 
 | ||||
|             if (this.logAfterFetch) { | ||||
|                 this.logAfterFetch = false; | ||||
|                 await CoreUtils.ignoreErrors(AddonModData.logView(this.database.id, this.database.name)); | ||||
| 
 | ||||
|                 // Store module viewed because this page also updates recent accessed items block.
 | ||||
|                 CoreCourse.storeModuleViewed(this.courseId, this.moduleId); | ||||
|             } | ||||
|             this.logView(); | ||||
|         } catch (error) { | ||||
|             if (!refresh) { | ||||
|                 // Some call failed, retry without using cache since it might be a new activity.
 | ||||
| @ -250,7 +248,7 @@ export class AddonModDataEntryPage implements OnInit, OnDestroy { | ||||
|         this.entryId = undefined; | ||||
|         this.entry = undefined; | ||||
|         this.entryLoaded = false; | ||||
|         this.logAfterFetch = true; | ||||
|         this.logView = CoreTime.once(() => this.performLogView()); // Log again after loading data.
 | ||||
| 
 | ||||
|         await this.fetchEntryData(); | ||||
|     } | ||||
| @ -310,7 +308,6 @@ export class AddonModDataEntryPage implements OnInit, OnDestroy { | ||||
|         this.entry = undefined; | ||||
|         this.entryId = undefined; | ||||
|         this.entryLoaded = false; | ||||
|         this.logAfterFetch = true; | ||||
| 
 | ||||
|         await this.fetchEntryData(); | ||||
|     } | ||||
| @ -422,6 +419,28 @@ export class AddonModDataEntryPage implements OnInit, OnDestroy { | ||||
|         AddonModData.invalidateEntryData(this.database!.id, this.entryId!); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Log view. | ||||
|      */ | ||||
|     protected async performLogView(): Promise<void> { | ||||
|         if (!this.database) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         await CoreUtils.ignoreErrors(AddonModData.logView(this.database.id)); | ||||
| 
 | ||||
|         // Store module viewed because this page also updates recent accessed items block.
 | ||||
|         CoreCourse.storeModuleViewed(this.courseId, this.moduleId); | ||||
| 
 | ||||
|         CoreAnalytics.logEvent({ | ||||
|             type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|             ws: 'mod_data_view_database', | ||||
|             name: this.database.name, | ||||
|             data: { id: this.entryId, databaseid: this.database.id, category: 'data' }, | ||||
|             url: `/mod/data/view.php?d=${this.database.id}&rid=${this.entryId}`, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|  | ||||
| @ -955,23 +955,19 @@ export class AddonModDataProvider { | ||||
|      * Report the database as being viewed. | ||||
|      * | ||||
|      * @param id Module ID. | ||||
|      * @param name Name of the data. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @returns Promise resolved when the WS call is successful. | ||||
|      */ | ||||
|     async logView(id: number, name?: string, siteId?: string): Promise<void> { | ||||
|     async logView(id: number, siteId?: string): Promise<void> { | ||||
|         const params: AddonModDataViewDatabaseWSParams = { | ||||
|             databaseid: id, | ||||
|         }; | ||||
| 
 | ||||
|         await CoreCourseLogHelper.logSingle( | ||||
|         await CoreCourseLogHelper.log( | ||||
|             'mod_data_view_database', | ||||
|             params, | ||||
|             AddonModDataProvider.COMPONENT, | ||||
|             id, | ||||
|             name, | ||||
|             'data', | ||||
|             {}, | ||||
|             siteId, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
| @ -37,8 +37,7 @@ export class AddonModFeedbackAttemptsSource extends CoreRoutedItemsManagerSource | ||||
|     anonymous?: AddonModFeedbackWSAnonAttempt[]; | ||||
|     anonymousTotal?: number; | ||||
|     groupInfo?: CoreGroupInfo; | ||||
| 
 | ||||
|     protected feedback?: AddonModFeedbackWSFeedback; | ||||
|     feedback?: AddonModFeedbackWSFeedback; | ||||
| 
 | ||||
|     constructor(courseId: number, cmId: number) { | ||||
|         super(); | ||||
|  | ||||
| @ -56,7 +56,7 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity | ||||
|     @Input() group = 0; | ||||
| 
 | ||||
|     component = AddonModFeedbackProvider.COMPONENT; | ||||
|     moduleName = 'feedback'; | ||||
|     pluginName = 'feedback'; | ||||
|     feedback?: AddonModFeedbackWSFeedback; | ||||
|     goPage?: number; | ||||
|     items: AddonModFeedbackItem[] = []; | ||||
| @ -140,7 +140,18 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity | ||||
|             return; // Shouldn't happen.
 | ||||
|         } | ||||
| 
 | ||||
|         await AddonModFeedback.logView(this.feedback.id, this.feedback.name); | ||||
|         await AddonModFeedback.logView(this.feedback.id); | ||||
| 
 | ||||
|         this.callAnalyticsLogEvent(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Call analytics. | ||||
|      */ | ||||
|     protected callAnalyticsLogEvent(): void { | ||||
|         this.analyticsLogEvent('mod_feedback_view_feedback', { | ||||
|             url: this.tab === 'analysis' ? `/mod/feedback/analysis.php?id=${this.module.id}` : undefined, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -429,11 +440,16 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity | ||||
|      * @param tabName New tab name. | ||||
|      */ | ||||
|     tabChanged(tabName: string): void { | ||||
|         const tabHasChanged = this.tab !== undefined && this.tab !== tabName; | ||||
|         this.tab = tabName; | ||||
| 
 | ||||
|         if (!this.tabsLoaded[this.tab]) { | ||||
|             this.loadContent(false, false, true); | ||||
|         } | ||||
| 
 | ||||
|         if (tabHasChanged) { | ||||
|             this.callAnalyticsLogEvent(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -27,6 +27,8 @@ import { | ||||
|     AddonModFeedbackWSFeedback, | ||||
| } from '../../services/feedback'; | ||||
| import { AddonModFeedbackAttempt, AddonModFeedbackFormItem, AddonModFeedbackHelper } from '../../services/feedback-helper'; | ||||
| import { CoreTime } from '@singletons/time'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays a feedback attempt review. | ||||
| @ -48,6 +50,7 @@ export class AddonModFeedbackAttemptPage implements OnInit, OnDestroy { | ||||
|     loaded = false; | ||||
| 
 | ||||
|     protected attemptId: number; | ||||
|     protected logView: () => void; | ||||
| 
 | ||||
|     constructor() { | ||||
|         this.cmId = CoreNavigator.getRequiredRouteNumberParam('cmId'); | ||||
| @ -60,6 +63,21 @@ export class AddonModFeedbackAttemptPage implements OnInit, OnDestroy { | ||||
|         ); | ||||
| 
 | ||||
|         this.attempts = new AddonModFeedbackAttemptsSwipeManager(source); | ||||
| 
 | ||||
|         this.logView = CoreTime.once(() => { | ||||
|             if (!this.feedback) { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             CoreAnalytics.logEvent({ | ||||
|                 type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|                 ws: 'mod_feedback_get_responses_analysis', | ||||
|                 name: this.feedback.name, | ||||
|                 data: { id: this.attemptId, feedbackid: this.feedback.id, category: 'feedback' }, | ||||
|                 url: `/mod/feedback/show_entries.php?id=${this.cmId}` + | ||||
|                     (this.attempt ? `userid=${this.attempt.userid}` : '' ) + `&showcompleted=${this.attemptId}`, | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -129,6 +147,8 @@ export class AddonModFeedbackAttemptPage implements OnInit, OnDestroy { | ||||
| 
 | ||||
|                 return attemptItem; | ||||
|             }).filter((itemData) => itemData); // Filter items with errors.
 | ||||
| 
 | ||||
|             this.logView(); | ||||
|         } catch (message) { | ||||
|             // Some call failed on fetch, go back.
 | ||||
|             CoreDomUtils.showErrorModalDefault(message, 'core.course.errorgetmodule', true); | ||||
|  | ||||
| @ -25,6 +25,8 @@ import { CoreDomUtils } from '@services/utils/dom'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| import { AddonModFeedbackAttemptItem, AddonModFeedbackAttemptsSource } from '../../classes/feedback-attempts-source'; | ||||
| import { AddonModFeedbackWSAnonAttempt, AddonModFeedbackWSAttempt } from '../../services/feedback'; | ||||
| import { CoreTime } from '@singletons/time'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays feedback attempts. | ||||
| @ -41,8 +43,25 @@ export class AddonModFeedbackAttemptsPage implements AfterViewInit, OnDestroy { | ||||
|     fetchFailed = false; | ||||
|     courseId?: number; | ||||
| 
 | ||||
|     protected logView: () => void; | ||||
| 
 | ||||
|     constructor(protected route: ActivatedRoute) { | ||||
|         this.promisedAttempts = new CorePromisedValue(); | ||||
| 
 | ||||
|         this.logView = CoreTime.once(() => { | ||||
|             const source = this.attempts?.getSource(); | ||||
|             if (!source || !source.feedback) { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             CoreAnalytics.logEvent({ | ||||
|                 type: CoreAnalyticsEventType.VIEW_ITEM_LIST, | ||||
|                 ws: 'mod_feedback_get_responses_analysis', | ||||
|                 name: source.feedback.name, | ||||
|                 data: { feedbackid: source.feedback.id, category: 'feedback' }, | ||||
|                 url: `/mod/feedback/show_entries.php?id=${source.CM_ID}`, | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     get attempts(): AddonModFeedbackAttemptsManager | null { | ||||
| @ -112,6 +131,8 @@ export class AddonModFeedbackAttemptsPage implements AfterViewInit, OnDestroy { | ||||
| 
 | ||||
|             await attempts.getSource().loadFeedback(); | ||||
|             await attempts.load(); | ||||
| 
 | ||||
|             this.logView(); | ||||
|         } catch (error) { | ||||
|             this.fetchFailed = true; | ||||
| 
 | ||||
|  | ||||
| @ -38,6 +38,7 @@ import { | ||||
| import { AddonModFeedbackFormItem, AddonModFeedbackHelper } from '../../services/feedback-helper'; | ||||
| import { AddonModFeedbackSync } from '../../services/feedback-sync'; | ||||
| import { AddonModFeedbackModuleHandlerService } from '../../services/handlers/module'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays feedback form. | ||||
| @ -122,7 +123,7 @@ export class AddonModFeedbackFormPage implements OnInit, OnDestroy, CanLeave { | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
|             await AddonModFeedback.logView(this.feedback.id, this.feedback.name, true); | ||||
|             await AddonModFeedback.logView(this.feedback.id, true); | ||||
| 
 | ||||
|             CoreCourse.checkModuleCompletion(this.courseId, this.module!.completiondata); | ||||
|         } catch { | ||||
| @ -263,6 +264,8 @@ export class AddonModFeedbackFormPage implements OnInit, OnDestroy, CanLeave { | ||||
|             const itemsCopy = CoreUtils.clone(this.items); // Copy the array to avoid modifications.
 | ||||
|             this.originalData = AddonModFeedbackHelper.getPageItemsResponses(itemsCopy); | ||||
|         } | ||||
| 
 | ||||
|         this.analyticsLogEvent(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -435,6 +438,40 @@ export class AddonModFeedbackFormPage implements OnInit, OnDestroy, CanLeave { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Log event in analytics. | ||||
|      */ | ||||
|     protected analyticsLogEvent(): void { | ||||
|         if (!this.feedback) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if (this.preview) { | ||||
|             CoreAnalytics.logEvent({ | ||||
|                 type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|                 ws: 'mod_feedback_get_items', | ||||
|                 name: this.feedback.name, | ||||
|                 data: { id: this.feedback.id, category: 'feedback' }, | ||||
|                 url: `/mod/feedback/print.php?id=${this.cmId}&courseid=${this.courseId}`, | ||||
|             }); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         let url = '/mod/feedback/complete.php'; | ||||
|         if (!this.completed) { | ||||
|             url += `?id=${this.cmId}` + (this.currentPage ? `&gopage=${this.currentPage}` : '') + `&courseid=${this.courseId}`; | ||||
|         } | ||||
| 
 | ||||
|         CoreAnalytics.logEvent({ | ||||
|             type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|             ws: this.completed ? 'mod_feedback_get_feedback_access_information' : 'mod_feedback_get_page_items', | ||||
|             name: this.feedback.name, | ||||
|             data: { id: this.feedback.id, category: 'feedback', page: this.currentPage }, | ||||
|             url, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|  | ||||
| @ -20,6 +20,8 @@ import { CoreDomUtils } from '@services/utils/dom'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| import { AddonModFeedback, AddonModFeedbackWSFeedback } from '../../services/feedback'; | ||||
| import { AddonModFeedbackHelper, AddonModFeedbackNonRespondent } from '../../services/feedback-helper'; | ||||
| import { CoreTime } from '@singletons/time'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays feedback non respondents. | ||||
| @ -33,6 +35,7 @@ export class AddonModFeedbackNonRespondentsPage implements OnInit { | ||||
|     protected cmId!: number; | ||||
|     protected feedback?: AddonModFeedbackWSFeedback; | ||||
|     protected page = 0; | ||||
|     protected logView: () => void; | ||||
| 
 | ||||
|     courseId!: number; | ||||
|     selectedGroup!: number; | ||||
| @ -43,6 +46,22 @@ export class AddonModFeedbackNonRespondentsPage implements OnInit { | ||||
|     loaded = false; | ||||
|     loadMoreError = false; | ||||
| 
 | ||||
|     constructor() { | ||||
|         this.logView = CoreTime.once(() => { | ||||
|             if (!this.feedback) { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             CoreAnalytics.logEvent({ | ||||
|                 type: CoreAnalyticsEventType.VIEW_ITEM_LIST, | ||||
|                 ws: 'mod_feedback_get_non_respondents', | ||||
|                 name: this.feedback.name, | ||||
|                 data: { feedbackid: this.feedback.id, category: 'feedback' }, | ||||
|                 url: `/mod/feedback/show_nonrespondents.php?id=${this.cmId}&courseid=${this.courseId}`, | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
| @ -81,6 +100,8 @@ export class AddonModFeedbackNonRespondentsPage implements OnInit { | ||||
|             this.selectedGroup = CoreGroups.validateGroupId(this.selectedGroup, this.groupInfo); | ||||
| 
 | ||||
|             await this.loadGroupUsers(this.selectedGroup); | ||||
| 
 | ||||
|             this.logView(); | ||||
|         } catch (message) { | ||||
|             CoreDomUtils.showErrorModalDefault(message, 'core.course.errorgetmodule', true); | ||||
| 
 | ||||
|  | ||||
| @ -1093,25 +1093,21 @@ export class AddonModFeedbackProvider { | ||||
|      * Report the feedback as being viewed. | ||||
|      * | ||||
|      * @param id Module ID. | ||||
|      * @param name Name of the feedback. | ||||
|      * @param formViewed True if form was viewed. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @returns Promise resolved when the WS call is successful. | ||||
|      */ | ||||
|     async logView(id: number, name?: string, formViewed: boolean = false, siteId?: string): Promise<void> { | ||||
|     async logView(id: number, formViewed: boolean = false, siteId?: string): Promise<void> { | ||||
|         const params: AddonModFeedbackViewFeedbackWSParams = { | ||||
|             feedbackid: id, | ||||
|             moduleviewed: formViewed, | ||||
|         }; | ||||
| 
 | ||||
|         await CoreCourseLogHelper.logSingle( | ||||
|         await CoreCourseLogHelper.log( | ||||
|             'mod_feedback_view_feedback', | ||||
|             params, | ||||
|             AddonModFeedbackProvider.COMPONENT, | ||||
|             id, | ||||
|             name, | ||||
|             'feedback', | ||||
|             { moduleviewed: params.moduleviewed }, | ||||
|             siteId, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
| @ -22,6 +22,7 @@ import { Md5 } from 'ts-md5'; | ||||
| import { AddonModFolder, AddonModFolderFolder, AddonModFolderProvider } from '../../services/folder'; | ||||
| import { AddonModFolderFolderFormattedData, AddonModFolderHelper } from '../../services/folder-helper'; | ||||
| import { AddonModFolderModuleHandlerService } from '../../services/handlers/module'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| 
 | ||||
| /** | ||||
|  * Component that displays a folder. | ||||
| @ -39,6 +40,7 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo | ||||
|     @Input() subfolder?: AddonModFolderFolderFormattedData; // Subfolder to show.
 | ||||
| 
 | ||||
|     component = AddonModFolderProvider.COMPONENT; | ||||
|     pluginName = 'folder'; | ||||
|     contents?: AddonModFolderFolderFormattedData; | ||||
| 
 | ||||
|     constructor(@Optional() courseContentsPage?: CoreCourseContentsPage) { | ||||
| @ -119,7 +121,9 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     protected async logActivity(): Promise<void> { | ||||
|         await AddonModFolder.logView(this.module.instance, this.module.name); | ||||
|         await CoreUtils.ignoreErrors(AddonModFolder.logView(this.module.instance)); | ||||
| 
 | ||||
|         this.analyticsLogEvent('mod_folder_view_folder'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -126,23 +126,19 @@ export class AddonModFolderProvider { | ||||
|      * Report a folder as being viewed. | ||||
|      * | ||||
|      * @param id Module ID. | ||||
|      * @param name Name of the folder. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @returns Promise resolved when the WS call is successful. | ||||
|      */ | ||||
|     async logView(id: number, name?: string, siteId?: string): Promise<void> { | ||||
|     async logView(id: number, siteId?: string): Promise<void> { | ||||
|         const params: AddonModFolderViewFolderWSParams = { | ||||
|             folderid: id, | ||||
|         }; | ||||
| 
 | ||||
|         await CoreCourseLogHelper.logSingle( | ||||
|         await CoreCourseLogHelper.log( | ||||
|             'mod_folder_view_folder', | ||||
|             params, | ||||
|             AddonModFolderProvider.COMPONENT, | ||||
|             id, | ||||
|             name, | ||||
|             'folder', | ||||
|             {}, | ||||
|             siteId, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
| @ -71,7 +71,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom | ||||
|     @ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent; | ||||
| 
 | ||||
|     component = AddonModForumProvider.COMPONENT; | ||||
|     moduleName = 'forum'; | ||||
|     pluginName = 'forum'; | ||||
|     descriptionNote?: string; | ||||
|     promisedDiscussions: CorePromisedValue<AddonModForumDiscussionsManager>; | ||||
|     discussionsItems: AddonModForumDiscussionItem[] = []; | ||||
| @ -708,12 +708,14 @@ class AddonModForumDiscussionsManager extends CoreListItemsManager<AddonModForum | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
|             await AddonModForum.logView(forum.id, forum.name); | ||||
|             await AddonModForum.logView(forum.id); | ||||
| 
 | ||||
|             CoreCourse.checkModuleCompletion(this.page.courseId, this.page.module.completiondata); | ||||
|         } catch { | ||||
|             // Ignore errors.
 | ||||
|         } | ||||
| 
 | ||||
|         this.page.analyticsLogEvent('mod_forum_view_forum'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -52,6 +52,7 @@ import { CoreForms } from '@singletons/form'; | ||||
| import { CoreFileEntry } from '@services/file-helper'; | ||||
| import { AddonModForumSharedPostFormData } from '../../pages/discussion/discussion'; | ||||
| import { CoreDom } from '@singletons/dom'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| 
 | ||||
| /** | ||||
|  * Components that shows a discussion post, its attachments and the action buttons allowed (reply, etc.). | ||||
| @ -141,6 +142,9 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges | ||||
|      * Deletes an online post. | ||||
|      */ | ||||
|     async deletePost(): Promise<void> { | ||||
|         // Log analytics even if the user cancels for consistency with LMS.
 | ||||
|         this.analyticsLogEvent('mod_forum_delete_post', `/mod/forum/post.php?delete=${this.post.id}`); | ||||
| 
 | ||||
|         try { | ||||
|             await CoreDomUtils.showDeleteConfirm('addon.mod_forum.deletesure'); | ||||
| 
 | ||||
| @ -290,6 +294,8 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges | ||||
|         } | ||||
| 
 | ||||
|         this.scrollToForm(); | ||||
| 
 | ||||
|         this.analyticsLogEvent('mod_forum_add_discussion_post', `/mod/forum/post.php?reply=${this.post.id}`); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -314,6 +320,8 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges | ||||
|             ); | ||||
| 
 | ||||
|             this.scrollToForm(); | ||||
| 
 | ||||
|             this.analyticsLogEvent('mod_forum_update_discussion_post', `/mod/forum/post.php?edit=${this.post.id}`); | ||||
|         } catch { | ||||
|             // Cancelled.
 | ||||
|         } | ||||
| @ -554,4 +562,24 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Log analytics event. | ||||
|      * | ||||
|      * @param wsName WS name. | ||||
|      * @param url URL. | ||||
|      */ | ||||
|     protected analyticsLogEvent(wsName: string, url: string): void { | ||||
|         if (this.post.id <= 0) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         CoreAnalytics.logEvent({ | ||||
|             type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|             ws: wsName, | ||||
|             name: this.post.subject, | ||||
|             data: { id: this.post.id, forumid: this.forum.id, category: 'forum' }, | ||||
|             url, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -51,6 +51,7 @@ import { | ||||
| import { AddonModForumHelper } from '../../services/forum-helper'; | ||||
| import { AddonModForumOffline } from '../../services/forum-offline'; | ||||
| import { AddonModForumSync, AddonModForumSyncProvider } from '../../services/forum-sync'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| 
 | ||||
| type SortType = 'flat-newest' | 'flat-oldest' | 'nested'; | ||||
| 
 | ||||
| @ -562,19 +563,7 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes | ||||
| 
 | ||||
|             if (forceMarkAsRead || (hasUnreadPosts && this.trackPosts)) { | ||||
|                 // Add log in Moodle and mark unread posts as readed.
 | ||||
|                 AddonModForum.logDiscussionView(this.discussionId, this.forumId || -1, this.forum.name).catch(() => { | ||||
|                     // Ignore errors.
 | ||||
|                 }).finally(() => { | ||||
|                     if (!this.courseId || !this.cmId) { | ||||
|                         return; | ||||
|                     } | ||||
| 
 | ||||
|                     // Trigger mark read posts.
 | ||||
|                     CoreEvents.trigger(AddonModForumProvider.MARK_READ_EVENT, { | ||||
|                         courseId: this.courseId, | ||||
|                         moduleId: this.cmId, | ||||
|                     }, CoreSites.getCurrentSiteId()); | ||||
|                 }); | ||||
|                 this.logDiscussionView(forceMarkAsRead); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| @ -854,6 +843,35 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes | ||||
|         return posts; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Log discussion as viewed. This will also mark the posts as read. | ||||
|      * | ||||
|      * @param logAnalytics Whether to log analytics too or not. | ||||
|      */ | ||||
|     protected async logDiscussionView(logAnalytics = false): Promise<void> { | ||||
|         await CoreUtils.ignoreErrors(AddonModForum.logDiscussionView(this.discussionId, this.forumId || -1)); | ||||
| 
 | ||||
|         if (logAnalytics) { | ||||
|             CoreAnalytics.logEvent({ | ||||
|                 type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|                 ws: 'mod_forum_view_forum_discussion', | ||||
|                 name: this.startingPost?.subject ?? this.forum.name ?? '', | ||||
|                 data: { id: this.discussionId, forumid: this.forumId, category: 'forum' }, | ||||
|                 url: `/mod/forum/discuss.php?d=${this.discussionId}` + (this.postId ? `#p${this.postId}` : ''), | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         if (!this.courseId || !this.cmId) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // Trigger mark read posts.
 | ||||
|         CoreEvents.trigger(AddonModForumProvider.MARK_READ_EVENT, { | ||||
|             courseId: this.courseId, | ||||
|             moduleId: this.cmId, | ||||
|         }, CoreSites.getCurrentSiteId()); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  | ||||
| @ -44,6 +44,8 @@ import { AddonModForumDiscussionsSwipeManager } from '../../classes/forum-discus | ||||
| import { ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router'; | ||||
| import { AddonModForumDiscussionsSource } from '../../classes/forum-discussions-source'; | ||||
| import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker'; | ||||
| import { CoreTime } from '@singletons/time'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| 
 | ||||
| type NewDiscussionData = { | ||||
|     subject: string; | ||||
| @ -105,8 +107,19 @@ export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy, CanLea | ||||
|     protected originalData?: Partial<NewDiscussionData>; | ||||
|     protected forceLeave = false; | ||||
|     protected initialGroupId?: number; | ||||
|     protected logView: () => void; | ||||
| 
 | ||||
|     constructor(protected route: ActivatedRoute, @Optional() protected splitView: CoreSplitViewComponent) {} | ||||
|     constructor(protected route: ActivatedRoute, @Optional() protected splitView: CoreSplitViewComponent) { | ||||
|         this.logView = CoreTime.once(() => { | ||||
|             CoreAnalytics.logEvent({ | ||||
|                 type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|                 ws: 'mod_forum_add_discussion', | ||||
|                 name: Translate.instant('addon.mod_forum.addanewdiscussion'), | ||||
|                 data: { id: this.forumId, category: 'forum' }, | ||||
|                 url: '/mod/forum/post.php', | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
| @ -309,6 +322,8 @@ export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy, CanLea | ||||
|             } | ||||
| 
 | ||||
|             this.showForm = true; | ||||
| 
 | ||||
|             this.logView(); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'addon.mod_forum.errorgetgroups', true); | ||||
| 
 | ||||
|  | ||||
| @ -996,23 +996,19 @@ export class AddonModForumProvider { | ||||
|      * Report a forum as being viewed. | ||||
|      * | ||||
|      * @param id Module ID. | ||||
|      * @param name Name of the forum. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @returns Promise resolved when the WS call is successful. | ||||
|      */ | ||||
|     logView(id: number, name?: string, siteId?: string): Promise<void> { | ||||
|     logView(id: number, siteId?: string): Promise<void> { | ||||
|         const params = { | ||||
|             forumid: id, | ||||
|         }; | ||||
| 
 | ||||
|         return CoreCourseLogHelper.logSingle( | ||||
|         return CoreCourseLogHelper.log( | ||||
|             'mod_forum_view_forum', | ||||
|             params, | ||||
|             AddonModForumProvider.COMPONENT, | ||||
|             id, | ||||
|             name, | ||||
|             'forum', | ||||
|             {}, | ||||
|             siteId, | ||||
|         ); | ||||
|     } | ||||
| @ -1022,23 +1018,19 @@ export class AddonModForumProvider { | ||||
|      * | ||||
|      * @param id Discussion ID. | ||||
|      * @param forumId Forum ID. | ||||
|      * @param name Name of the forum. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @returns Promise resolved when the WS call is successful. | ||||
|      */ | ||||
|     logDiscussionView(id: number, forumId: number, name?: string, siteId?: string): Promise<void> { | ||||
|     logDiscussionView(id: number, forumId: number, siteId?: string): Promise<void> { | ||||
|         const params = { | ||||
|             discussionid: id, | ||||
|         }; | ||||
| 
 | ||||
|         return CoreCourseLogHelper.logSingle( | ||||
|         return CoreCourseLogHelper.log( | ||||
|             'mod_forum_view_forum_discussion', | ||||
|             params, | ||||
|             AddonModForumProvider.COMPONENT, | ||||
|             forumId, | ||||
|             name, | ||||
|             'forum', | ||||
|             params, | ||||
|             siteId, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
| @ -56,6 +56,7 @@ import { | ||||
| import { AddonModGlossaryModuleHandlerService } from '../../services/handlers/module'; | ||||
| import { AddonModGlossaryPrefetchHandler } from '../../services/handlers/prefetch'; | ||||
| import { AddonModGlossaryModePickerPopoverComponent } from '../mode-picker/mode-picker'; | ||||
| import { CoreTime } from '@singletons/time'; | ||||
| 
 | ||||
| /** | ||||
|  * Component that displays a glossary entry page. | ||||
| @ -71,7 +72,7 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity | ||||
|     @ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent; | ||||
| 
 | ||||
|     component = AddonModGlossaryProvider.COMPONENT; | ||||
|     moduleName = 'glossary'; | ||||
|     pluginName = 'glossary'; | ||||
| 
 | ||||
|     canAdd = false; | ||||
|     loadMoreError = false; | ||||
| @ -86,6 +87,7 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity | ||||
|     protected sourceUnsubscribe?: () => void; | ||||
|     protected observers?: CoreEventObserver[]; | ||||
|     protected checkCompletionAfterLog = false; // Use CoreListItemsManager log system instead.
 | ||||
|     protected logSearch?: () => void; | ||||
| 
 | ||||
|     getDivider?: (entry: AddonModGlossaryEntry) => string; | ||||
|     showDivider: (entry: AddonModGlossaryEntry, previous?: AddonModGlossaryEntry) => boolean = () => false; | ||||
| @ -226,6 +228,10 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity | ||||
| 
 | ||||
|         this.hasOfflineRatings = hasOfflineRatings; | ||||
|         this.hasOffline = this.hasOfflineEntries || this.hasOfflineRatings; | ||||
| 
 | ||||
|         if (this.isSearch && this.logSearch) { | ||||
|             this.logSearch(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -424,11 +430,23 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity | ||||
|     search(query: string): void { | ||||
|         this.loadingMessage = Translate.instant('core.searching'); | ||||
|         this.showLoading = true; | ||||
|         this.logSearch = CoreTime.once(() => this.performLogSearch(query)); | ||||
| 
 | ||||
|         this.entries?.getSource().search(query); | ||||
|         this.loadContent(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Log search. | ||||
|      * | ||||
|      * @param query Text entered on the search box. | ||||
|      */ | ||||
|     protected async performLogSearch(query: string): Promise<void> { | ||||
|         this.analyticsLogEvent('mod_glossary_get_entries_by_search', { | ||||
|             data: { mode: 'search', hook: query, fullsearch: 1 }, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
| @ -482,12 +500,14 @@ class AddonModGlossaryEntriesManager extends CoreListItemsManager<AddonModGlossa | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
|             await AddonModGlossary.logView(glossary.id, viewMode, glossary.name); | ||||
|             await AddonModGlossary.logView(glossary.id, viewMode); | ||||
| 
 | ||||
|             CoreCourse.checkModuleCompletion(this.page.courseId, this.page.module.completiondata); | ||||
|         } catch { | ||||
|             // Ignore errors.
 | ||||
|         } | ||||
| 
 | ||||
|         this.page.analyticsLogEvent('mod_glossary_view_glossary', { data: { mode: viewMode } }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -40,6 +40,7 @@ import { | ||||
| } from '../../services/glossary'; | ||||
| import { AddonModGlossaryHelper } from '../../services/glossary-helper'; | ||||
| import { AddonModGlossaryOffline } from '../../services/glossary-offline'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays the edit form. | ||||
| @ -76,6 +77,7 @@ export class AddonModGlossaryEditPage implements OnInit, CanLeave { | ||||
| 
 | ||||
|     originalData?: AddonModGlossaryFormData; | ||||
| 
 | ||||
|     protected entry?: AddonModGlossaryEntry; | ||||
|     protected syncId?: string; | ||||
|     protected syncObserver?: CoreEventObserver; | ||||
|     protected isDestroyed = false; | ||||
| @ -99,6 +101,7 @@ export class AddonModGlossaryEditPage implements OnInit, CanLeave { | ||||
|             } else if (entrySlug) { | ||||
|                 const { entry } = await AddonModGlossary.getEntry(Number(entrySlug)); | ||||
| 
 | ||||
|                 this.entry = entry; | ||||
|                 this.editorExtraParams.timecreated = entry.timecreated; | ||||
|                 this.handler = new AddonModGlossaryOnlineFormHandler(this, entry); | ||||
|             } else { | ||||
| @ -127,6 +130,18 @@ export class AddonModGlossaryEditPage implements OnInit, CanLeave { | ||||
|             await this.handler.loadData(this.glossary); | ||||
| 
 | ||||
|             this.loaded = true; | ||||
| 
 | ||||
|             if (this.handler instanceof AddonModGlossaryOfflineFormHandler) { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             CoreAnalytics.logEvent({ | ||||
|                 type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|                 ws: 'mod_glossary_get_glossaries_by_courses', | ||||
|                 name: this.glossary.name, | ||||
|                 data: { id: this.glossary.id, category: 'glossary' }, | ||||
|                 url: '/mod/glossary/edit.php' + (this.entry ? `?cmid=${this.cmId}&id=${this.entry.id}` : ''), | ||||
|             }); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'addon.mod_glossary.errorloadingglossary', true); | ||||
| 
 | ||||
|  | ||||
| @ -39,6 +39,8 @@ import { | ||||
|     AddonModGlossaryProvider, | ||||
|     GLOSSARY_ENTRY_UPDATED, | ||||
| } from '../../services/glossary'; | ||||
| import { CoreTime } from '@singletons/time'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays a glossary entry. | ||||
| @ -70,7 +72,19 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy { | ||||
|     courseId!: number; | ||||
|     cmId!: number; | ||||
| 
 | ||||
|     constructor(@Optional() protected splitView: CoreSplitViewComponent, protected route: ActivatedRoute) {} | ||||
|     protected logView: () => void; | ||||
| 
 | ||||
|     constructor(@Optional() protected splitView: CoreSplitViewComponent, protected route: ActivatedRoute) { | ||||
|         this.logView = CoreTime.once(async () => { | ||||
|             if (!this.onlineEntry || !this.glossary || !this.componentId) { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             await CoreUtils.ignoreErrors(AddonModGlossary.logEntryView(this.onlineEntry.id, this.componentId)); | ||||
| 
 | ||||
|             this.analyticsLogEvent('mod_glossary_get_entry_by_id', `/mod/glossary/showentry.php?eid=${this.onlineEntry.id}`); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     get entry(): AddonModGlossaryEntry | AddonModGlossaryOfflineEntry | undefined { | ||||
|         return this.onlineEntry ?? this.offlineEntry; | ||||
| @ -128,12 +142,6 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy { | ||||
|         try { | ||||
|             if (onlineEntryId) { | ||||
|                 await this.loadOnlineEntry(onlineEntryId); | ||||
| 
 | ||||
|                 if (!this.glossary || !this.componentId) { | ||||
|                     return; | ||||
|                 } | ||||
| 
 | ||||
|                 await CoreUtils.ignoreErrors(AddonModGlossary.logEntryView(onlineEntryId, this.componentId, this.glossary?.name)); | ||||
|             } else if (offlineEntryTimeCreated) { | ||||
|                 await this.loadOfflineEntry(offlineEntryTimeCreated); | ||||
|             } | ||||
| @ -161,6 +169,12 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy { | ||||
|      * Delete entry. | ||||
|      */ | ||||
|     async deleteEntry(): Promise<void> { | ||||
|         // Log analytics even if the user cancels for consistency with LMS.
 | ||||
|         this.analyticsLogEvent( | ||||
|             'mod_glossary_delete_entry', | ||||
|             `/mod/glossary/deleteentry.php?id=${this.glossary?.id}&mode=delete&entry=${this.onlineEntry?.id}`, | ||||
|         ); | ||||
| 
 | ||||
|         const glossaryId = this.glossary?.id; | ||||
|         const cancelled = await CoreUtils.promiseFails( | ||||
|             CoreDomUtils.showConfirm(Translate.instant('addon.mod_glossary.areyousuredelete')), | ||||
| @ -250,6 +264,8 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy { | ||||
|             this.canEdit = canUpdateEntries && !!result.permissions?.canupdate; | ||||
| 
 | ||||
|             await this.loadGlossary(); | ||||
| 
 | ||||
|             this.logView(); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'addon.mod_glossary.errorloadingentry', true); | ||||
|         } | ||||
| @ -321,6 +337,26 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy { | ||||
|         AddonModGlossary.invalidateEntry(this.onlineEntry.id); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Log analytics event. | ||||
|      * | ||||
|      * @param wsName WS name. | ||||
|      * @param url URL. | ||||
|      */ | ||||
|     protected analyticsLogEvent(wsName: string, url: string): void { | ||||
|         if (!this.onlineEntry || !this.glossary) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         CoreAnalytics.logEvent({ | ||||
|             type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|             ws: wsName, | ||||
|             name: this.onlineEntry.concept, | ||||
|             data: { id: this.onlineEntry.id, glossaryid: this.glossary.id, category: 'glossary' }, | ||||
|             url, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  | ||||
| @ -1020,23 +1020,19 @@ export class AddonModGlossaryProvider { | ||||
|      * | ||||
|      * @param glossaryId Glossary ID. | ||||
|      * @param mode The mode in which the glossary was viewed. | ||||
|      * @param name Name of the glossary. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      */ | ||||
|     async logView(glossaryId: number, mode: string, name?: string, siteId?: string): Promise<void> { | ||||
|     async logView(glossaryId: number, mode: string, siteId?: string): Promise<void> { | ||||
|         const params: AddonModGlossaryViewGlossaryWSParams = { | ||||
|             id: glossaryId, | ||||
|             mode: mode, | ||||
|         }; | ||||
| 
 | ||||
|         await CoreCourseLogHelper.logSingle( | ||||
|         await CoreCourseLogHelper.log( | ||||
|             'mod_glossary_view_glossary', | ||||
|             params, | ||||
|             AddonModGlossaryProvider.COMPONENT, | ||||
|             glossaryId, | ||||
|             name, | ||||
|             'glossary', | ||||
|             { mode }, | ||||
|             siteId, | ||||
|         ); | ||||
|     } | ||||
| @ -1046,22 +1042,18 @@ export class AddonModGlossaryProvider { | ||||
|      * | ||||
|      * @param entryId Entry ID. | ||||
|      * @param glossaryId Glossary ID. | ||||
|      * @param name Name of the glossary. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      */ | ||||
|     async logEntryView(entryId: number, glossaryId: number, name?: string, siteId?: string): Promise<void> { | ||||
|     async logEntryView(entryId: number, glossaryId: number, siteId?: string): Promise<void> { | ||||
|         const params: AddonModGlossaryViewEntryWSParams = { | ||||
|             id: entryId, | ||||
|         }; | ||||
| 
 | ||||
|         await CoreCourseLogHelper.logSingle( | ||||
|         await CoreCourseLogHelper.log( | ||||
|             'mod_glossary_view_entry', | ||||
|             params, | ||||
|             AddonModGlossaryProvider.COMPONENT, | ||||
|             glossaryId, | ||||
|             name, | ||||
|             'glossary', | ||||
|             { entryid: entryId }, | ||||
|             siteId, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
| @ -63,7 +63,7 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv | ||||
|     @Output() onActivityFinish = new EventEmitter<boolean>(); | ||||
| 
 | ||||
|     component = AddonModH5PActivityProvider.COMPONENT; | ||||
|     moduleName = 'h5pactivity'; | ||||
|     pluginName = 'h5pactivity'; | ||||
| 
 | ||||
|     h5pActivity?: AddonModH5PActivityData; // The H5P activity object.
 | ||||
|     accessInfo?: AddonModH5PActivityAccessInfo; // Info about the user capabilities.
 | ||||
| @ -441,9 +441,11 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv | ||||
|         this.playing = true; | ||||
| 
 | ||||
|         // Mark the activity as viewed.
 | ||||
|         await AddonModH5PActivity.logView(this.h5pActivity.id, this.h5pActivity.name, this.siteId); | ||||
|         await AddonModH5PActivity.logView(this.h5pActivity.id, this.siteId); | ||||
| 
 | ||||
|         this.checkCompletion(); | ||||
| 
 | ||||
|         this.analyticsLogEvent('mod_h5pactivity_view_h5pactivity'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -25,6 +25,8 @@ import { | ||||
|     AddonModH5PActivityData, | ||||
|     AddonModH5PActivityAttemptResults, | ||||
| } from '../../services/h5pactivity'; | ||||
| import { CoreTime } from '@singletons/time'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays results of an attempt. | ||||
| @ -45,7 +47,28 @@ export class AddonModH5PActivityAttemptResultsPage implements OnInit { | ||||
|     cmId!: number; | ||||
| 
 | ||||
|     protected attemptId!: number; | ||||
|     protected fetchSuccess = false; | ||||
|     protected logView: () => void; | ||||
| 
 | ||||
|     constructor() { | ||||
|         this.logView = CoreTime.once(async () => { | ||||
|             if (!this.h5pActivity) { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             await CoreUtils.ignoreErrors(AddonModH5PActivity.logViewReport( | ||||
|                 this.h5pActivity.id, | ||||
|                 { attemptId: this.attemptId }, | ||||
|             )); | ||||
| 
 | ||||
|             CoreAnalytics.logEvent({ | ||||
|                 type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|                 ws: 'mod_h5pactivity_log_report_viewed', | ||||
|                 name: this.h5pActivity.name, | ||||
|                 data: { id: this.h5pActivity.id, attemptid: this.attemptId, category: 'h5pactivity' }, | ||||
|                 url: `/mod/h5pactivity/report.php?a=${this.h5pActivity.id}&attemptid=${this.attemptId}`, | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
| @ -92,14 +115,7 @@ export class AddonModH5PActivityAttemptResultsPage implements OnInit { | ||||
| 
 | ||||
|             await this.fetchUserProfile(); | ||||
| 
 | ||||
|             if (!this.fetchSuccess) { | ||||
|                 this.fetchSuccess = true; | ||||
|                 CoreUtils.ignoreErrors(AddonModH5PActivity.logViewReport( | ||||
|                     this.h5pActivity.id, | ||||
|                     this.h5pActivity.name, | ||||
|                     { attemptId: this.attemptId }, | ||||
|                 )); | ||||
|             } | ||||
|             this.logView(); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'Error loading attempt.'); | ||||
|         } finally { | ||||
|  | ||||
| @ -26,6 +26,8 @@ import { | ||||
|     AddonModH5PActivityData, | ||||
|     AddonModH5PActivityUserAttempts, | ||||
| } from '../../services/h5pactivity'; | ||||
| import { CoreTime } from '@singletons/time'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays user attempts of a certain user. | ||||
| @ -46,7 +48,28 @@ export class AddonModH5PActivityUserAttemptsPage implements OnInit { | ||||
|     isCurrentUser = false; | ||||
| 
 | ||||
|     protected userId!: number; | ||||
|     protected fetchSuccess = false; | ||||
|     protected logView: () => void; | ||||
| 
 | ||||
|     constructor() { | ||||
|         this.logView = CoreTime.once(async () => { | ||||
|             if (!this.h5pActivity) { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             await CoreUtils.ignoreErrors(AddonModH5PActivity.logViewReport( | ||||
|                 this.h5pActivity.id, | ||||
|                 { userId: this.userId }, | ||||
|             )); | ||||
| 
 | ||||
|             CoreAnalytics.logEvent({ | ||||
|                 type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|                 ws: 'mod_h5pactivity_log_report_viewed', | ||||
|                 name: this.h5pActivity.name, | ||||
|                 data: { id: this.h5pActivity.id, userid: this.userId, category: 'h5pactivity' }, | ||||
|                 url: `/mod/h5pactivity/report.php?a=${this.h5pActivity.id}&userid=${this.userId}`, | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
| @ -94,14 +117,7 @@ export class AddonModH5PActivityUserAttemptsPage implements OnInit { | ||||
|                 this.fetchUserProfile(), | ||||
|             ]); | ||||
| 
 | ||||
|             if (!this.fetchSuccess) { | ||||
|                 this.fetchSuccess = true; | ||||
|                 CoreUtils.ignoreErrors(AddonModH5PActivity.logViewReport( | ||||
|                     this.h5pActivity.id, | ||||
|                     this.h5pActivity.name, | ||||
|                     { userId: this.userId }, | ||||
|                 )); | ||||
|             } | ||||
|             this.logView(); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'Error loading attempts.'); | ||||
|         } finally { | ||||
|  | ||||
| @ -25,6 +25,8 @@ import { | ||||
|     AddonModH5PActivityProvider, | ||||
|     AddonModH5PActivityUserAttempts, | ||||
| } from '../../services/h5pactivity'; | ||||
| import { CoreTime } from '@singletons/time'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays all users that can attempt an H5P activity. | ||||
| @ -45,7 +47,25 @@ export class AddonModH5PActivityUsersAttemptsPage implements OnInit { | ||||
|     canLoadMore = false; | ||||
| 
 | ||||
|     protected page = 0; | ||||
|     protected fetchSuccess = false; | ||||
|     protected logView: () => void; | ||||
| 
 | ||||
|     constructor() { | ||||
|         this.logView = CoreTime.once(async () => { | ||||
|             if (!this.h5pActivity) { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             await CoreUtils.ignoreErrors(AddonModH5PActivity.logViewReport(this.h5pActivity.id)); | ||||
| 
 | ||||
|             CoreAnalytics.logEvent({ | ||||
|                 type: CoreAnalyticsEventType.VIEW_ITEM_LIST, | ||||
|                 ws: 'mod_h5pactivity_log_report_viewed', | ||||
|                 name: this.h5pActivity.name, | ||||
|                 data: { id: this.h5pActivity.id, category: 'h5pactivity' }, | ||||
|                 url: `/mod/h5pactivity/report.php?a=${this.h5pActivity.id}`, | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
| @ -90,10 +110,7 @@ export class AddonModH5PActivityUsersAttemptsPage implements OnInit { | ||||
|                 this.fetchUsers(refresh), | ||||
|             ]); | ||||
| 
 | ||||
|             if (!this.fetchSuccess) { | ||||
|                 this.fetchSuccess = true; | ||||
|                 CoreUtils.ignoreErrors(AddonModH5PActivity.logViewReport(this.h5pActivity.id, this.h5pActivity.name)); | ||||
|             } | ||||
|             this.logView(); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'Error loading attempts.'); | ||||
|         } finally { | ||||
|  | ||||
| @ -776,23 +776,19 @@ export class AddonModH5PActivityProvider { | ||||
|      * Report an H5P activity as being viewed. | ||||
|      * | ||||
|      * @param id H5P activity ID. | ||||
|      * @param name Name of the activity. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @returns Promise resolved when the WS call is successful. | ||||
|      */ | ||||
|     logView(id: number, name?: string, siteId?: string): Promise<void> { | ||||
|     logView(id: number, siteId?: string): Promise<void> { | ||||
|         const params: AddonModH5PActivityViewH5pactivityWSParams = { | ||||
|             h5pactivityid: id, | ||||
|         }; | ||||
| 
 | ||||
|         return CoreCourseLogHelper.logSingle( | ||||
|         return CoreCourseLogHelper.log( | ||||
|             'mod_h5pactivity_view_h5pactivity', | ||||
|             params, | ||||
|             AddonModH5PActivityProvider.COMPONENT, | ||||
|             id, | ||||
|             name, | ||||
|             'h5pactivity', | ||||
|             {}, | ||||
|             siteId, | ||||
|         ); | ||||
|     } | ||||
| @ -801,11 +797,10 @@ export class AddonModH5PActivityProvider { | ||||
|      * Report an H5P activity report as being viewed. | ||||
|      * | ||||
|      * @param id H5P activity ID. | ||||
|      * @param name Name of the activity. | ||||
|      * @param options Options. | ||||
|      * @returns Promise resolved when the WS call is successful. | ||||
|      */ | ||||
|     async logViewReport(id: number, name?: string,  options: AddonModH5PActivityViewReportOptions = {}): Promise<void> { | ||||
|     async logViewReport(id: number, options: AddonModH5PActivityViewReportOptions = {}): Promise<void> { | ||||
|         const site = await CoreSites.getSite(options.siteId); | ||||
| 
 | ||||
|         if (!site.wsAvailable('mod_h5pactivity_log_report_viewed')) { | ||||
| @ -819,14 +814,11 @@ export class AddonModH5PActivityProvider { | ||||
|             attemptid: options.attemptId, | ||||
|         }; | ||||
| 
 | ||||
|         return CoreCourseLogHelper.logSingle( | ||||
|         return CoreCourseLogHelper.log( | ||||
|             'mod_h5pactivity_log_report_viewed', | ||||
|             params, | ||||
|             AddonModH5PActivityProvider.COMPONENT, | ||||
|             id, | ||||
|             name, | ||||
|             'h5pactivity', | ||||
|             {}, | ||||
|             site.getId(), | ||||
|         ); | ||||
|     } | ||||
|  | ||||
| @ -18,6 +18,7 @@ import { CoreCourseContentsPage } from '@features/course/pages/contents/contents | ||||
| import { CoreCourse } from '@features/course/services/course'; | ||||
| import { CoreNavigator } from '@services/navigator'; | ||||
| import { AddonModImscpProvider, AddonModImscp, AddonModImscpTocItem } from '../../services/imscp'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| 
 | ||||
| /** | ||||
|  * Component that displays a IMSCP. | ||||
| @ -30,6 +31,7 @@ import { AddonModImscpProvider, AddonModImscp, AddonModImscpTocItem } from '../. | ||||
| export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceComponent implements OnInit { | ||||
| 
 | ||||
|     component = AddonModImscpProvider.COMPONENT; | ||||
|     pluginName = 'imscp'; | ||||
| 
 | ||||
|     items: AddonModImscpTocItem[] = []; | ||||
|     hasStarted = false; | ||||
| @ -100,7 +102,9 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     protected async logActivity(): Promise<void> { | ||||
|         await AddonModImscp.logView(this.module.instance, this.module.name); | ||||
|         await CoreUtils.ignoreErrors(AddonModImscp.logView(this.module.instance)); | ||||
| 
 | ||||
|         this.analyticsLogEvent('mod_imscp_view_imscp'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -271,7 +271,6 @@ export class AddonModImscpProvider { | ||||
|      * Report a IMSCP as being viewed. | ||||
|      * | ||||
|      * @param id Module ID. | ||||
|      * @param name Name of the imscp. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @returns Promise resolved when the WS call is successful. | ||||
|      */ | ||||
| @ -280,14 +279,11 @@ export class AddonModImscpProvider { | ||||
|             imscpid: id, | ||||
|         }; | ||||
| 
 | ||||
|         await CoreCourseLogHelper.logSingle( | ||||
|         await CoreCourseLogHelper.log( | ||||
|             'mod_imscp_view_imscp', | ||||
|             params, | ||||
|             AddonModImscpProvider.COMPONENT, | ||||
|             id, | ||||
|             name, | ||||
|             'imscp', | ||||
|             {}, | ||||
|             siteId, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
| @ -66,7 +66,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo | ||||
|     @Input() action?: string; // The "action" to display first.
 | ||||
| 
 | ||||
|     component = AddonModLessonProvider.COMPONENT; | ||||
|     moduleName = 'lesson'; | ||||
|     pluginName = 'lesson'; | ||||
| 
 | ||||
|     lesson?: AddonModLessonLessonWSData; // The lesson.
 | ||||
|     selectedTab?: number; // The initial selected tab.
 | ||||
| @ -372,7 +372,16 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         await AddonModLesson.logViewLesson(this.lesson.id, this.password, this.lesson.name); | ||||
|         await CoreUtils.ignoreErrors(AddonModLesson.logViewLesson(this.lesson.id, this.password)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Call analytics. | ||||
|      */ | ||||
|     protected callAnalyticsLogEvent(): void { | ||||
|         this.analyticsLogEvent('mod_lesson_view_lesson', { | ||||
|             url: this.selectedTab === 1 ? `/mod/lesson/report.php?id=${this.module.id}&action=reportoverview` : undefined, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -435,13 +444,19 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo | ||||
|      * First tab selected. | ||||
|      */ | ||||
|     indexSelected(): void { | ||||
|         const tabHasChanged = this.selectedTab !== 0; | ||||
|         this.selectedTab = 0; | ||||
| 
 | ||||
|         if (tabHasChanged) { | ||||
|             this.callAnalyticsLogEvent(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Reports tab selected. | ||||
|      */ | ||||
|     reportsSelected(): void { | ||||
|         const tabHasChanged = this.selectedTab !== 1; | ||||
|         this.selectedTab = 1; | ||||
| 
 | ||||
|         if (!this.groupInfo) { | ||||
| @ -449,6 +464,10 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo | ||||
|                 CoreDomUtils.showErrorModalDefault(error, 'Error getting report.'); | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         if (tabHasChanged) { | ||||
|             this.callAnalyticsLogEvent(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -54,6 +54,7 @@ import { | ||||
| import { AddonModLessonOffline } from '../../services/lesson-offline'; | ||||
| import { AddonModLessonSync } from '../../services/lesson-sync'; | ||||
| import { CoreFormFields, CoreForms } from '@singletons/form'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that allows attempting and reviewing a lesson. | ||||
| @ -446,6 +447,8 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy, CanLeave { | ||||
|                 this.reviewPageId = Number(params.pageid); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         this.logPageLoaded(AddonModLessonProvider.LESSON_EOL, Translate.instant('addon.mod_lesson.congratulations')); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -615,6 +618,44 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy, CanLeave { | ||||
|         } else { | ||||
|             this.showRetake = false; | ||||
|         } | ||||
| 
 | ||||
|         this.logPageLoaded(pageId, data.page?.title ?? ''); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Log page loaded. | ||||
|      * | ||||
|      * @param pageId Page ID. | ||||
|      */ | ||||
|     protected logPageLoaded(pageId: number, title: string): void { | ||||
|         if (!this.lesson) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         CoreAnalytics.logEvent({ | ||||
|             type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|             ws: 'mod_lesson_get_page_data', | ||||
|             name: this.lesson.name + ': ' + title, | ||||
|             data: { id: this.lesson.id, pageid: pageId, category: 'lesson' }, | ||||
|             url: `/mod/lesson/view.php?id=${this.lesson.id}&pageid=${pageId}`, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Log continue page. | ||||
|      */ | ||||
|     protected logContinuePageLoaded(): void { | ||||
|         if (!this.lesson) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         CoreAnalytics.logEvent({ | ||||
|             type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|             ws: 'mod_lesson_process_page', | ||||
|             name: this.lesson.name + ': ' + Translate.instant('addon.mod_lesson.continue'), | ||||
|             data: { id: this.lesson.id, category: 'lesson' }, | ||||
|             url: '/mod/lesson/continue.php', | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -715,6 +756,8 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy, CanLeave { | ||||
|                     pageId: result.newpageid, | ||||
|                 }); | ||||
|             } | ||||
| 
 | ||||
|             this.logContinuePageLoaded(); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'Error processing page'); | ||||
|         } finally { | ||||
|  | ||||
| @ -35,6 +35,7 @@ import { | ||||
| } from '../../services/lesson'; | ||||
| import { AddonModLessonAnswerData, AddonModLessonHelper } from '../../services/lesson-helper'; | ||||
| import { CoreTime } from '@singletons/time'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays a retake made by a certain user. | ||||
| @ -59,6 +60,11 @@ export class AddonModLessonUserRetakePage implements OnInit { | ||||
|     protected userId?: number; // User ID to see the retakes.
 | ||||
|     protected retakeNumber?: number; // Number of the initial retake to see.
 | ||||
|     protected previousSelectedRetake?: number; // To be able to detect the previous selected retake when it has changed.
 | ||||
|     protected logView: () => void; | ||||
| 
 | ||||
|     constructor() { | ||||
|         this.logView = CoreTime.once(() => this.performLogView()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
| @ -93,6 +99,8 @@ export class AddonModLessonUserRetakePage implements OnInit { | ||||
| 
 | ||||
|         try { | ||||
|             await this.setRetake(retakeNumber); | ||||
| 
 | ||||
|             this.performLogView(); | ||||
|         } catch (error) { | ||||
|             this.selectedRetake = this.previousSelectedRetake ?? this.selectedRetake; | ||||
|             CoreDomUtils.showErrorModal(CoreUtils.addDataNotDownloadedError(error, 'Error getting attempt.')); | ||||
| @ -160,6 +168,8 @@ export class AddonModLessonUserRetakePage implements OnInit { | ||||
|             this.student.profileimageurl = user?.profileimageurl; | ||||
| 
 | ||||
|             await this.setRetake(this.selectedRetake); | ||||
| 
 | ||||
|             this.logView(); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'Error getting data.', true); | ||||
|         } | ||||
| @ -243,6 +253,23 @@ export class AddonModLessonUserRetakePage implements OnInit { | ||||
|         return formattedData; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Log view. | ||||
|      */ | ||||
|     protected performLogView(): void { | ||||
|         if (!this.lesson) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         CoreAnalytics.logEvent({ | ||||
|             type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|             ws: 'mod_lesson_get_user_attempt', | ||||
|             name: this.lesson.name + ': ' + Translate.instant('addon.mod_lesson.detailedstats'), | ||||
|             data: { id: this.lesson.id, userid: this.userId, try: this.selectedRetake, category: 'lesson' }, | ||||
|             url: `/mod/lesson/report.php?id=${this.cmId}&action=reportdetail&userid=${this.userId}&try=${this.selectedRetake}`, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  | ||||
| @ -2951,11 +2951,10 @@ export class AddonModLessonProvider { | ||||
|      * | ||||
|      * @param id Module ID. | ||||
|      * @param password Lesson password (if any). | ||||
|      * @param name Name of the assign. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @returns Promise resolved when the WS call is successful. | ||||
|      */ | ||||
|     async logViewLesson(id: number, password?: string, name?: string, siteId?: string): Promise<void> { | ||||
|     async logViewLesson(id: number, password?: string, siteId?: string): Promise<void> { | ||||
|         const params: AddonModLessonViewLessonWSParams = { | ||||
|             lessonid: id, | ||||
|         }; | ||||
| @ -2964,14 +2963,11 @@ export class AddonModLessonProvider { | ||||
|             params.password = password; | ||||
|         } | ||||
| 
 | ||||
|         await CoreCourseLogHelper.logSingle( | ||||
|         await CoreCourseLogHelper.log( | ||||
|             'mod_lesson_view_lesson', | ||||
|             params, | ||||
|             AddonModLessonProvider.COMPONENT, | ||||
|             id, | ||||
|             name, | ||||
|             'lesson', | ||||
|             {}, | ||||
|             siteId, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
| @ -30,7 +30,7 @@ import { AddonModLtiHelper } from '../../services/lti-helper'; | ||||
| export class AddonModLtiIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit { | ||||
| 
 | ||||
|     component = AddonModLtiProvider.COMPONENT; | ||||
|     moduleName = 'lti'; | ||||
|     pluginName = 'lti'; | ||||
|     displayDescription = false; | ||||
| 
 | ||||
|     lti?: AddonModLtiLti; // The LTI object.
 | ||||
|  | ||||
| @ -22,6 +22,7 @@ import { CoreDomUtils } from '@services/utils/dom'; | ||||
| import { makeSingleton } from '@singletons'; | ||||
| import { CoreEvents } from '@singletons/events'; | ||||
| import { AddonModLti, AddonModLtiLti } from './lti'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| 
 | ||||
| /** | ||||
|  * Service that provides some helper functions for LTI. | ||||
| @ -86,7 +87,7 @@ export class AddonModLtiHelperProvider { | ||||
|             const launchData = await AddonModLti.getLtiLaunchData(lti.id); | ||||
| 
 | ||||
|             // "View" LTI without blocking the UI.
 | ||||
|             this.logViewAndCheckCompletion(courseId, module, lti.id, lti.name, siteId); | ||||
|             this.logViewAndCheckCompletion(courseId, module, lti.id, siteId); | ||||
| 
 | ||||
|             // Launch LTI.
 | ||||
|             return AddonModLti.launch(launchData.endpoint, launchData.parameters); | ||||
| @ -111,16 +112,23 @@ export class AddonModLtiHelperProvider { | ||||
|         courseId: number, | ||||
|         module: CoreCourseModuleData, | ||||
|         ltiId: number, | ||||
|         name?: string, | ||||
|         siteId?: string, | ||||
|     ): Promise<void> { | ||||
|         try { | ||||
|             await AddonModLti.logView(ltiId, name, siteId); | ||||
|             await AddonModLti.logView(ltiId,siteId); | ||||
| 
 | ||||
|             CoreCourse.checkModuleCompletion(courseId, module.completiondata); | ||||
|         } catch { | ||||
|             // Ignore errors.
 | ||||
|         } | ||||
| 
 | ||||
|         CoreAnalytics.logEvent({ | ||||
|             type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|             ws: 'mod_lti_view_lti', | ||||
|             name: module.name, | ||||
|             data: { id: module.instance, category: 'lti' }, | ||||
|             url: `/mod/lti/view.php?id=${module.id}`, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -272,14 +272,11 @@ export class AddonModLtiProvider { | ||||
|             ltiid: id, | ||||
|         }; | ||||
| 
 | ||||
|         return CoreCourseLogHelper.logSingle( | ||||
|         return CoreCourseLogHelper.log( | ||||
|             'mod_lti_view_lti', | ||||
|             params, | ||||
|             AddonModLtiProvider.COMPONENT, | ||||
|             id, | ||||
|             name, | ||||
|             'lti', | ||||
|             {}, | ||||
|             siteId, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
| @ -31,6 +31,7 @@ import { AddonModPageHelper } from '../../services/page-helper'; | ||||
| export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComponent implements OnInit { | ||||
| 
 | ||||
|     component = AddonModPageProvider.COMPONENT; | ||||
|     pluginName = 'page'; | ||||
|     contents?: string; | ||||
|     displayDescription = false; | ||||
|     displayTimemodified = true; | ||||
| @ -114,7 +115,9 @@ export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComp | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     protected async logActivity(): Promise<void> { | ||||
|         await AddonModPage.logView(this.module.instance, this.module.name); | ||||
|         await CoreUtils.ignoreErrors(AddonModPage.logView(this.module.instance)); | ||||
| 
 | ||||
|         this.analyticsLogEvent('mod_page_view_page'); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -141,23 +141,19 @@ export class AddonModPageProvider { | ||||
|      * Report a page as being viewed. | ||||
|      * | ||||
|      * @param pageid Module ID. | ||||
|      * @param name Name of the page. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @returns Promise resolved when the WS call is successful. | ||||
|      */ | ||||
|     logView(pageid: number, name?: string, siteId?: string): Promise<void> { | ||||
|     logView(pageid: number, siteId?: string): Promise<void> { | ||||
|         const params: AddonModPageViewPageWSParams = { | ||||
|             pageid, | ||||
|         }; | ||||
| 
 | ||||
|         return CoreCourseLogHelper.logSingle( | ||||
|         return CoreCourseLogHelper.log( | ||||
|             'mod_page_view_page', | ||||
|             params, | ||||
|             AddonModPageProvider.COMPONENT, | ||||
|             pageid, | ||||
|             name, | ||||
|             'page', | ||||
|             {}, | ||||
|             siteId, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
| @ -57,7 +57,7 @@ import { | ||||
| export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit, OnDestroy { | ||||
| 
 | ||||
|     component = AddonModQuizProvider.COMPONENT; | ||||
|     moduleName = 'quiz'; | ||||
|     pluginName = 'quiz'; | ||||
|     quiz?: AddonModQuizQuizData; // The quiz.
 | ||||
|     now?: number; // Current time.
 | ||||
|     syncTime?: string; // Last synchronization time.
 | ||||
| @ -386,7 +386,9 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp | ||||
|             return; // Shouldn't happen.
 | ||||
|         } | ||||
| 
 | ||||
|         await AddonModQuiz.logViewQuiz(this.quiz.id, this.quiz.name); | ||||
|         await CoreUtils.ignoreErrors(AddonModQuiz.logViewQuiz(this.quiz.id)); | ||||
| 
 | ||||
|         this.analyticsLogEvent('mod_quiz_view_quiz'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -49,6 +49,7 @@ import { CoreDom } from '@singletons/dom'; | ||||
| import { CoreTime } from '@singletons/time'; | ||||
| import { CoreDirectivesRegistry } from '@singletons/directives-registry'; | ||||
| import { CoreWSError } from '@classes/errors/wserror'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that allows attempting a quiz. | ||||
| @ -534,9 +535,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { | ||||
|         // @todo MOBILE-4350: This is called before getting the attempt data in sequential quizzes as a workaround for a bug
 | ||||
|         // in the LMS. Once the bug has been fixed, this should be reverted.
 | ||||
|         if (this.isSequential) { | ||||
|             await CoreUtils.ignoreErrors( | ||||
|                 AddonModQuiz.logViewAttempt(this.attempt.id, page, this.preflightData, this.offline, this.quiz), | ||||
|             ); | ||||
|             await this.logViewPage(page); | ||||
|         } | ||||
| 
 | ||||
|         const data = await AddonModQuiz.getAttemptData(this.attempt.id, page, this.preflightData, { | ||||
| @ -569,15 +568,55 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { | ||||
|         // Mark the page as viewed.
 | ||||
|         if (!this.isSequential) { | ||||
|             // @todo MOBILE-4350: Undo workaround.
 | ||||
|             CoreUtils.ignoreErrors( | ||||
|                 AddonModQuiz.logViewAttempt(this.attempt.id, page, this.preflightData, this.offline, this.quiz), | ||||
|             ); | ||||
|             await this.logViewPage(page); | ||||
|         } | ||||
| 
 | ||||
|         // Start looking for changes.
 | ||||
|         this.autoSave.startCheckChangesProcess(this.quiz, this.attempt, this.preflightData, this.offline); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Log view a page. | ||||
|      * | ||||
|      * @param page Page viewed. | ||||
|      */ | ||||
|     protected async logViewPage(page: number): Promise<void> { | ||||
|         if (!this.quiz || !this.attempt) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         await CoreUtils.ignoreErrors(AddonModQuiz.logViewAttempt(this.attempt.id, page, this.preflightData, this.offline)); | ||||
| 
 | ||||
|         CoreAnalytics.logEvent({ | ||||
|             type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|             ws: 'mod_quiz_view_attempt', | ||||
|             name: this.quiz.name, | ||||
|             data: { id: this.attempt.id, quizid: this.quiz.id, page, category: 'quiz' }, | ||||
|             url: `/mod/quiz/attempt.php?attempt=${this.attempt.id}&cmid=${this.cmId}` + (page > 0 ? `&page=${page}` : ''), | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Log view summary. | ||||
|      */ | ||||
|     protected async logViewSummary(): Promise<void> { | ||||
|         if (!this.quiz || !this.attempt) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         await CoreUtils.ignoreErrors( | ||||
|             AddonModQuiz.logViewAttemptSummary(this.attempt.id, this.preflightData, this.quiz.id), | ||||
|         ); | ||||
| 
 | ||||
|         CoreAnalytics.logEvent({ | ||||
|             type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|             ws: 'mod_quiz_view_attempt_summary', | ||||
|             name: this.quiz.name, | ||||
|             data: { id: this.attempt.id, quizid: this.quiz.id, category: 'quiz' }, | ||||
|             url: `/mod/quiz/summary.php?attempt=${this.attempt.id}&cmid=${this.cmId}`, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Refresh attempt data. | ||||
|      */ | ||||
| @ -618,10 +657,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { | ||||
| 
 | ||||
|         this.dueDateWarning = AddonModQuiz.getAttemptDueDateWarning(this.quiz, this.attempt); | ||||
| 
 | ||||
|         // Log summary as viewed.
 | ||||
|         CoreUtils.ignoreErrors( | ||||
|             AddonModQuiz.logViewAttemptSummary(this.attempt.id, this.preflightData, this.quiz.id, this.quiz.name), | ||||
|         ); | ||||
|         this.logViewSummary(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -37,6 +37,7 @@ import { | ||||
|     AddonModQuizWSAdditionalData, | ||||
| } from '../../services/quiz'; | ||||
| import { AddonModQuizHelper } from '../../services/quiz-helper'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that allows reviewing a quiz attempt. | ||||
| @ -73,11 +74,15 @@ export class AddonModQuizReviewPage implements OnInit { | ||||
|     protected attemptId!: number; // The attempt being reviewed.
 | ||||
|     protected currentPage!: number; // The current page being reviewed.
 | ||||
|     protected options?: AddonModQuizCombinedReviewOptions; // Review options.
 | ||||
|     protected fetchSuccess = false; | ||||
|     protected logView: () => void; | ||||
| 
 | ||||
|     constructor( | ||||
|         protected elementRef: ElementRef, | ||||
|     ) { | ||||
|         this.logView = CoreTime.once(() => this.performLogView(true, { | ||||
|             showAllDisabled: !this.showAll, | ||||
|             page: this.currentPage, | ||||
|         })); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -127,6 +132,8 @@ export class AddonModQuizReviewPage implements OnInit { | ||||
| 
 | ||||
|         try { | ||||
|             await this.loadPage(page); | ||||
| 
 | ||||
|             this.performLogView(false, { page }); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'addon.mod_quiz.errorgetquestions', true); | ||||
|         } finally { | ||||
| @ -156,12 +163,7 @@ export class AddonModQuizReviewPage implements OnInit { | ||||
|             // Load questions.
 | ||||
|             await this.loadPage(this.currentPage); | ||||
| 
 | ||||
|             if (!this.fetchSuccess) { | ||||
|                 this.fetchSuccess = true; | ||||
|                 CoreUtils.ignoreErrors( | ||||
|                     AddonModQuiz.logViewAttemptReview(this.attemptId, this.quiz.id, this.quiz.name), | ||||
|                 ); | ||||
|             } | ||||
|             this.logView(); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'addon.mod_quiz.errorgetquiz', true); | ||||
|         } | ||||
| @ -325,11 +327,13 @@ export class AddonModQuizReviewPage implements OnInit { | ||||
|     /** | ||||
|      * Switch mode: all questions in same page OR one page at a time. | ||||
|      */ | ||||
|     switchMode(): void { | ||||
|     async switchMode(): Promise<void> { | ||||
|         this.showAll = !this.showAll; | ||||
| 
 | ||||
|         // Load all questions or first page, depending on the mode.
 | ||||
|         this.loadPage(this.showAll ? -1 : 0); | ||||
|         await this.loadPage(this.showAll ? -1 : 0); | ||||
| 
 | ||||
|         this.performLogView(false, { showAllDisabled: !this.showAll }); | ||||
|     } | ||||
| 
 | ||||
|     async openNavigation(): Promise<void> { | ||||
| @ -351,6 +355,37 @@ export class AddonModQuizReviewPage implements OnInit { | ||||
|         this.changePage(modalData.page, modalData.slot); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Perform log view. | ||||
|      * | ||||
|      * @param logInLMS Whether to log in LMS too or only in analytics. | ||||
|      * @param options Other options. | ||||
|      */ | ||||
|     protected async performLogView(logInLMS = false, options: LogViewOptions = {}): Promise<void> { | ||||
|         if (!this.quiz) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if (logInLMS) { | ||||
|             await CoreUtils.ignoreErrors(AddonModQuiz.logViewAttemptReview(this.attemptId, this.quiz.id)); | ||||
|         } | ||||
| 
 | ||||
|         let url = `/mod/quiz/review.php?attempt=${this.attemptId}&cmid=${this.cmId}`; | ||||
|         if (options.showAllDisabled) { | ||||
|             url += '&showall=0'; | ||||
|         } else if (options.page && options.page > 0) { | ||||
|             url += `&page=${ options.page}`; | ||||
|         } | ||||
| 
 | ||||
|         CoreAnalytics.logEvent({ | ||||
|             type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|             ws: 'mod_quiz_view_attempt_review', | ||||
|             name: this.quiz.name, | ||||
|             data: { id: this.attemptId, quizid: this.quiz.id, page: options.page, category: 'quiz' }, | ||||
|             url: url, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| /** | ||||
| @ -359,3 +394,8 @@ export class AddonModQuizReviewPage implements OnInit { | ||||
| type QuizQuestion = CoreQuestionQuestionParsed & { | ||||
|     readableMark?: string; | ||||
| }; | ||||
| 
 | ||||
| type LogViewOptions = { | ||||
|     page?: number; // Page being viewed (if viewing pages);
 | ||||
|     showAllDisabled?: boolean; // Whether the showAll option has just been disabled.
 | ||||
| }; | ||||
|  | ||||
| @ -404,13 +404,11 @@ export class AddonModQuizSyncProvider extends CoreCourseActivitySyncBaseProvider | ||||
| 
 | ||||
|         if (!finish) { | ||||
|             // Answers sent, now set the current page.
 | ||||
|             // Don't pass the quiz instance because we don't want to trigger a Firebase event in this case.
 | ||||
|             await CoreUtils.ignoreErrors(AddonModQuiz.logViewAttempt( | ||||
|                 onlineAttempt.id, | ||||
|                 offlineAttempt.currentpage, | ||||
|                 preflightData, | ||||
|                 false, | ||||
|                 undefined, | ||||
|                 siteId, | ||||
|             )); | ||||
|         } | ||||
|  | ||||
| @ -21,7 +21,6 @@ import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; | ||||
| import { CoreCourseCommonModWSOptions } from '@features/course/services/course'; | ||||
| import { CoreCourseLogHelper } from '@features/course/services/log-helper'; | ||||
| import { CoreGradesFormattedItem, CoreGradesHelper } from '@features/grades/services/grades-helper'; | ||||
| import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications'; | ||||
| import { | ||||
|     CoreQuestion, | ||||
|     CoreQuestionQuestionParsed, | ||||
| @ -1535,7 +1534,6 @@ export class AddonModQuizProvider { | ||||
|      * @param page Page number. | ||||
|      * @param preflightData Preflight required data (like password). | ||||
|      * @param offline Whether attempt is offline. | ||||
|      * @param quiz Quiz instance. If set, a Firebase event will be stored. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @returns Promise resolved when the WS call is successful. | ||||
|      */ | ||||
| @ -1544,7 +1542,6 @@ export class AddonModQuizProvider { | ||||
|         page: number = 0, | ||||
|         preflightData: Record<string, string> = {}, | ||||
|         offline?: boolean, | ||||
|         quiz?: AddonModQuizQuizWSData, | ||||
|         siteId?: string, | ||||
|     ): Promise<void> { | ||||
|         const site = await CoreSites.getSite(siteId); | ||||
| @ -1564,16 +1561,6 @@ export class AddonModQuizProvider { | ||||
|         if (offline) { | ||||
|             promises.push(AddonModQuizOffline.setAttemptCurrentPage(attemptId, page, site.getId())); | ||||
|         } | ||||
|         if (quiz) { | ||||
|             CorePushNotifications.logViewEvent( | ||||
|                 quiz.id, | ||||
|                 quiz.name, | ||||
|                 'quiz', | ||||
|                 'mod_quiz_view_attempt', | ||||
|                 { attemptid: attemptId, page }, | ||||
|                 siteId, | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         await Promise.all(promises); | ||||
|     } | ||||
| @ -1583,23 +1570,19 @@ export class AddonModQuizProvider { | ||||
|      * | ||||
|      * @param attemptId Attempt ID. | ||||
|      * @param quizId Quiz ID. | ||||
|      * @param name Name of the quiz. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @returns Promise resolved when the WS call is successful. | ||||
|      */ | ||||
|     logViewAttemptReview(attemptId: number, quizId: number, name?: string, siteId?: string): Promise<void> { | ||||
|     logViewAttemptReview(attemptId: number, quizId: number, siteId?: string): Promise<void> { | ||||
|         const params: AddonModQuizViewAttemptReviewWSParams = { | ||||
|             attemptid: attemptId, | ||||
|         }; | ||||
| 
 | ||||
|         return CoreCourseLogHelper.logSingle( | ||||
|         return CoreCourseLogHelper.log( | ||||
|             'mod_quiz_view_attempt_review', | ||||
|             params, | ||||
|             AddonModQuizProvider.COMPONENT, | ||||
|             quizId, | ||||
|             name, | ||||
|             'quiz', | ||||
|             params, | ||||
|             siteId, | ||||
|         ); | ||||
|     } | ||||
| @ -1610,7 +1593,6 @@ export class AddonModQuizProvider { | ||||
|      * @param attemptId Attempt ID. | ||||
|      * @param preflightData Preflight required data (like password). | ||||
|      * @param quizId Quiz ID. | ||||
|      * @param name Name of the quiz. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @returns Promise resolved when the WS call is successful. | ||||
|      */ | ||||
| @ -1618,7 +1600,6 @@ export class AddonModQuizProvider { | ||||
|         attemptId: number, | ||||
|         preflightData: Record<string, string>, | ||||
|         quizId: number, | ||||
|         name?: string, | ||||
|         siteId?: string, | ||||
|     ): Promise<void> { | ||||
|         const params: AddonModQuizViewAttemptSummaryWSParams = { | ||||
| @ -1630,14 +1611,11 @@ export class AddonModQuizProvider { | ||||
|             ), | ||||
|         }; | ||||
| 
 | ||||
|         return CoreCourseLogHelper.logSingle( | ||||
|         return CoreCourseLogHelper.log( | ||||
|             'mod_quiz_view_attempt_summary', | ||||
|             params, | ||||
|             AddonModQuizProvider.COMPONENT, | ||||
|             quizId, | ||||
|             name, | ||||
|             'quiz', | ||||
|             { attemptid: attemptId }, | ||||
|             siteId, | ||||
|         ); | ||||
|     } | ||||
| @ -1646,23 +1624,19 @@ export class AddonModQuizProvider { | ||||
|      * Report a quiz as being viewed. | ||||
|      * | ||||
|      * @param id Module ID. | ||||
|      * @param name Name of the quiz. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @returns Promise resolved when the WS call is successful. | ||||
|      */ | ||||
|     logViewQuiz(id: number, name?: string, siteId?: string): Promise<void> { | ||||
|     logViewQuiz(id: number, siteId?: string): Promise<void> { | ||||
|         const params: AddonModQuizViewQuizWSParams = { | ||||
|             quizid: id, | ||||
|         }; | ||||
| 
 | ||||
|         return CoreCourseLogHelper.logSingle( | ||||
|         return CoreCourseLogHelper.log( | ||||
|             'mod_quiz_view_quiz', | ||||
|             params, | ||||
|             AddonModQuizProvider.COMPONENT, | ||||
|             id, | ||||
|             name, | ||||
|             'quiz', | ||||
|             {}, | ||||
|             siteId, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
| @ -47,6 +47,7 @@ import { CorePlatform } from '@services/platform'; | ||||
| export class AddonModResourceIndexComponent extends CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy { | ||||
| 
 | ||||
|     component = AddonModResourceProvider.COMPONENT; | ||||
|     pluginName = 'resource'; | ||||
| 
 | ||||
|     mode = ''; | ||||
|     src = ''; | ||||
| @ -188,7 +189,9 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     protected async logActivity(): Promise<void> { | ||||
|         await AddonModResource.logView(this.module.instance, this.module.name); | ||||
|         await CoreUtils.ignoreErrors(AddonModResource.logView(this.module.instance)); | ||||
| 
 | ||||
|         this.analyticsLogEvent('mod_resource_view_resource'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -28,6 +28,7 @@ import { CoreUtilsOpenFileOptions } from '@services/utils/utils'; | ||||
| import { makeSingleton, Translate } from '@singletons'; | ||||
| import { CorePath } from '@singletons/path'; | ||||
| import { AddonModResource, AddonModResourceProvider } from './resource'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| 
 | ||||
| /** | ||||
|  * Service that provides helper functions for resources. | ||||
| @ -206,6 +207,14 @@ export class AddonModResourceHelperProvider { | ||||
|             } catch { | ||||
|                 // Ignore errors.
 | ||||
|             } | ||||
| 
 | ||||
|             CoreAnalytics.logEvent({ | ||||
|                 type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|                 ws: 'mod_resource_view_resource', | ||||
|                 name: module.name, | ||||
|                 data: { id: module.instance, category: 'resource' }, | ||||
|                 url: `/mod/resource/view.php?id=${module.id}`, | ||||
|             }); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'addon.mod_resource.errorwhileloadingthecontent', true); | ||||
|         } finally { | ||||
|  | ||||
| @ -146,23 +146,19 @@ export class AddonModResourceProvider { | ||||
|      * Report the resource as being viewed. | ||||
|      * | ||||
|      * @param id Module ID. | ||||
|      * @param name Name of the resource. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @returns Promise resolved when the WS call is successful. | ||||
|      */ | ||||
|     async logView(id: number, name?: string, siteId?: string): Promise<void> { | ||||
|     async logView(id: number, siteId?: string): Promise<void> { | ||||
|         const params: AddonModResourceViewResourceWSParams = { | ||||
|             resourceid: id, | ||||
|         }; | ||||
| 
 | ||||
|         await CoreCourseLogHelper.logSingle( | ||||
|         await CoreCourseLogHelper.log( | ||||
|             'mod_resource_view_resource', | ||||
|             params, | ||||
|             AddonModResourceProvider.COMPONENT, | ||||
|             id, | ||||
|             name, | ||||
|             'resource', | ||||
|             {}, | ||||
|             siteId, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
| @ -57,7 +57,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom | ||||
|     @Input() autoPlayData?: AddonModScormAutoPlayData; // Data to use to play the SCORM automatically.
 | ||||
| 
 | ||||
|     component = AddonModScormProvider.COMPONENT; | ||||
|     moduleName = 'scorm'; | ||||
|     pluginName = 'scorm'; | ||||
| 
 | ||||
|     scorm?: AddonModScormScorm; // The SCORM object.
 | ||||
|     currentOrganization: Partial<AddonModScormOrganization> & { identifier: string} = { | ||||
| @ -361,7 +361,9 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom | ||||
|             return; // Shouldn't happen.
 | ||||
|         } | ||||
| 
 | ||||
|         await AddonModScorm.logView(this.scorm.id, this.scorm.name); | ||||
|         await CoreUtils.ignoreErrors(AddonModScorm.logView(this.scorm.id)); | ||||
| 
 | ||||
|         this.analyticsLogEvent('mod_scorm_view_scorm'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -35,6 +35,7 @@ import { | ||||
| } from '../../services/scorm'; | ||||
| import { AddonModScormHelper, AddonModScormTOCScoWithIcon } from '../../services/scorm-helper'; | ||||
| import { AddonModScormSync } from '../../services/scorm-sync'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that allows playing a SCORM. | ||||
| @ -442,8 +443,7 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy { | ||||
|             this.markCompleted(sco); | ||||
|         } | ||||
| 
 | ||||
|         // Trigger SCO launch event.
 | ||||
|         CoreUtils.ignoreErrors(AddonModScorm.logLaunchSco(this.scorm.id, sco.id, this.scorm.name)); | ||||
|         this.logEvent(sco.id); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -581,6 +581,27 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy { | ||||
|         })); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Log event. | ||||
|      */ | ||||
|     protected async logEvent(scoId: number): Promise<void> { | ||||
|         await CoreUtils.ignoreErrors(AddonModScorm.logLaunchSco(this.scorm.id, scoId)); | ||||
| 
 | ||||
|         let url = '/mod/scorm/player.php'; | ||||
|         if (this.scorm.popup) { | ||||
|             url += `?a=${this.scorm.id}¤torg=${this.organizationId}&scoid=${scoId}` + | ||||
|                 `&display=popup&mode=${this.mode}`; | ||||
|         } | ||||
| 
 | ||||
|         CoreAnalytics.logEvent({ | ||||
|             type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|             ws: 'mod_scorm_get_scorm_user_data', | ||||
|             name: this.scorm.name, | ||||
|             data: { id: this.scorm.id, scoid: scoId, organization: this.organizationId, category: 'scorm' }, | ||||
|             url, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|  | ||||
| @ -1420,24 +1420,20 @@ export class AddonModScormProvider { | ||||
|      * | ||||
|      * @param scormId SCORM ID. | ||||
|      * @param scoId SCO ID. | ||||
|      * @param name Name of the SCORM. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @returns Promise resolved when the WS call is successful. | ||||
|      */ | ||||
|     logLaunchSco(scormId: number, scoId: number, name?: string, siteId?: string): Promise<void> { | ||||
|     logLaunchSco(scormId: number, scoId: number, siteId?: string): Promise<void> { | ||||
|         const params: AddonModScormLaunchScoWSParams = { | ||||
|             scormid: scormId, | ||||
|             scoid: scoId, | ||||
|         }; | ||||
| 
 | ||||
|         return CoreCourseLogHelper.logSingle( | ||||
|         return CoreCourseLogHelper.log( | ||||
|             'mod_scorm_launch_sco', | ||||
|             params, | ||||
|             AddonModScormProvider.COMPONENT, | ||||
|             scormId, | ||||
|             name, | ||||
|             'scorm', | ||||
|             { scoid: scoId }, | ||||
|             siteId, | ||||
|         ); | ||||
|     } | ||||
| @ -1446,23 +1442,19 @@ export class AddonModScormProvider { | ||||
|      * Report a SCORM as being viewed. | ||||
|      * | ||||
|      * @param id Module ID. | ||||
|      * @param name Name of the SCORM. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @returns Promise resolved when the WS call is successful. | ||||
|      */ | ||||
|     logView(id: number, name?: string, siteId?: string): Promise<void> { | ||||
|     logView(id: number, siteId?: string): Promise<void> { | ||||
|         const params: AddonModScormViewScormWSParams = { | ||||
|             scormid: id, | ||||
|         }; | ||||
| 
 | ||||
|         return CoreCourseLogHelper.logSingle( | ||||
|         return CoreCourseLogHelper.log( | ||||
|             'mod_scorm_view_scorm', | ||||
|             params, | ||||
|             AddonModScormProvider.COMPONENT, | ||||
|             id, | ||||
|             name, | ||||
|             'scorm', | ||||
|             {}, | ||||
|             siteId, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
| @ -38,6 +38,7 @@ import { | ||||
|     AddonModSurveySyncProvider, | ||||
|     AddonModSurveySyncResult, | ||||
| } from '../../services/survey-sync'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| 
 | ||||
| /** | ||||
|  * Component that displays a survey. | ||||
| @ -50,7 +51,7 @@ import { | ||||
| export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit { | ||||
| 
 | ||||
|     component = AddonModSurveyProvider.COMPONENT; | ||||
|     moduleName = 'survey'; | ||||
|     pluginName = 'survey'; | ||||
| 
 | ||||
|     survey?: AddonModSurveySurvey; | ||||
|     questions: AddonModSurveyQuestionFormatted[] = []; | ||||
| @ -168,7 +169,9 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo | ||||
|             return; // Shouldn't happen.
 | ||||
|         } | ||||
| 
 | ||||
|         await AddonModSurvey.logView(this.survey.id, this.survey.name); | ||||
|         await CoreUtils.ignoreErrors(AddonModSurvey.logView(this.survey.id)); | ||||
| 
 | ||||
|         this.analyticsLogEvent('mod_survey_view_survey'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -208,7 +208,6 @@ export class AddonModSurveyProvider { | ||||
|      * Report the survey as being viewed. | ||||
|      * | ||||
|      * @param id Module ID. | ||||
|      * @param name Name of the assign. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @returns Promise resolved when the WS call is successful. | ||||
|      */ | ||||
| @ -217,14 +216,11 @@ export class AddonModSurveyProvider { | ||||
|             surveyid: id, | ||||
|         }; | ||||
| 
 | ||||
|         await CoreCourseLogHelper.logSingle( | ||||
|         await CoreCourseLogHelper.log( | ||||
|             'mod_survey_view_survey', | ||||
|             params, | ||||
|             AddonModSurveyProvider.COMPONENT, | ||||
|             id, | ||||
|             name, | ||||
|             'survey', | ||||
|             {}, | ||||
|             siteId, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
| @ -34,6 +34,7 @@ import { AddonModUrlHelper } from '../../services/url-helper'; | ||||
| export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceComponent implements OnInit { | ||||
| 
 | ||||
|     component = AddonModUrlProvider.COMPONENT; | ||||
|     pluginName = 'url'; | ||||
| 
 | ||||
|     url?: string; | ||||
|     name?: string; | ||||
| @ -153,12 +154,14 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo | ||||
|      */ | ||||
|     protected async logView(): Promise<void> { | ||||
|         try { | ||||
|             await AddonModUrl.logView(this.module.instance, this.module.name); | ||||
|             await AddonModUrl.logView(this.module.instance); | ||||
| 
 | ||||
|             this.checkCompletion(); | ||||
|         } catch { | ||||
|             // Ignore errors.
 | ||||
|         } | ||||
| 
 | ||||
|         this.analyticsLogEvent('mod_url_view_url'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -26,6 +26,7 @@ import { makeSingleton } from '@singletons'; | ||||
| import { AddonModUrlIndexComponent } from '../../components/index/index'; | ||||
| import { AddonModUrl } from '../url'; | ||||
| import { AddonModUrlHelper } from '../url-helper'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| 
 | ||||
| /** | ||||
|  * Handler to support url modules. | ||||
| @ -64,14 +65,7 @@ export class AddonModUrlModuleHandlerService extends CoreModuleHandlerBase imple | ||||
|          * @param courseId The course ID. | ||||
|          */ | ||||
|         const openUrl = async (module: CoreCourseModuleData, courseId: number): Promise<void> => { | ||||
|             try { | ||||
|                 if (module.instance) { | ||||
|                     await AddonModUrl.logView(module.instance, module.name); | ||||
|                     CoreCourse.checkModuleCompletion(module.course, module.completiondata); | ||||
|                 } | ||||
|             } catch { | ||||
|                 // Ignore errors.
 | ||||
|             } | ||||
|             await this.logView(module); | ||||
| 
 | ||||
|             CoreCourse.storeModuleViewed(courseId, module.id); | ||||
| 
 | ||||
| @ -196,5 +190,27 @@ export class AddonModUrlModuleHandlerService extends CoreModuleHandlerBase imple | ||||
|         return !iconUrl?.startsWith('assets/img/files/'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Log module viewed. | ||||
|      */ | ||||
|     protected async logView(module: CoreCourseModuleData): Promise<void> { | ||||
|         try { | ||||
|             if (module.instance) { | ||||
|                 await AddonModUrl.logView(module.instance); | ||||
|                 CoreCourse.checkModuleCompletion(module.course, module.completiondata); | ||||
|             } | ||||
|         } catch { | ||||
|             // Ignore errors.
 | ||||
|         } | ||||
| 
 | ||||
|         CoreAnalytics.logEvent({ | ||||
|             type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|             ws: 'mod_url_view_url', | ||||
|             name: module.name, | ||||
|             data: { id: module.instance, category: 'url' }, | ||||
|             url: `/mod/url/view.php?id=${module.id}`, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| export const AddonModUrlModuleHandler = makeSingleton(AddonModUrlModuleHandlerService); | ||||
|  | ||||
| @ -210,23 +210,19 @@ export class AddonModUrlProvider { | ||||
|      * Report the url as being viewed. | ||||
|      * | ||||
|      * @param id Module ID. | ||||
|      * @param name Name of the assign. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @returns Promise resolved when the WS call is successful. | ||||
|      */ | ||||
|     logView(id: number, name?: string, siteId?: string): Promise<void> { | ||||
|     logView(id: number, siteId?: string): Promise<void> { | ||||
|         const params: AddonModUrlViewUrlWSParams = { | ||||
|             urlid: id, | ||||
|         }; | ||||
| 
 | ||||
|         return CoreCourseLogHelper.logSingle( | ||||
|         return CoreCourseLogHelper.log( | ||||
|             'mod_url_view_url', | ||||
|             params, | ||||
|             AddonModUrlProvider.COMPONENT, | ||||
|             id, | ||||
|             name, | ||||
|             'url', | ||||
|             {}, | ||||
|             siteId, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
| @ -75,7 +75,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp | ||||
| 
 | ||||
|     component = AddonModWikiProvider.COMPONENT; | ||||
|     componentId?: number; | ||||
|     moduleName = 'wiki'; | ||||
|     pluginName = 'wiki'; | ||||
|     groupWiki = false; | ||||
| 
 | ||||
|     isOnline = false; | ||||
| @ -327,9 +327,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp | ||||
| 
 | ||||
|                     await this.showLoadingAndFetch(true, false); | ||||
| 
 | ||||
|                     if (this.currentPage && this.wiki) { | ||||
|                         CoreUtils.ignoreErrors(AddonModWiki.logPageView(this.currentPage, this.wiki.id, this.wiki.name)); | ||||
|                     } | ||||
|                     this.currentPage && this.logPageViewed(this.currentPage); | ||||
|                 }, CoreSites.getCurrentSiteId()); | ||||
|             } | ||||
| 
 | ||||
| @ -443,12 +441,60 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp | ||||
|             return; // Shouldn't happen.
 | ||||
|         } | ||||
| 
 | ||||
|         if (!this.pageId) { | ||||
|             await AddonModWiki.logView(this.wiki.id, this.wiki.name); | ||||
|         } else { | ||||
|         if (this.pageId) { | ||||
|             // View page.
 | ||||
|             this.checkCompletionAfterLog = false; | ||||
|             CoreUtils.ignoreErrors(AddonModWiki.logPageView(this.pageId, this.wiki.id, this.wiki.name)); | ||||
|             await this.logPageViewed(this.pageId); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         await AddonModWiki.logView(this.wiki.id); | ||||
| 
 | ||||
|         if (this.groupId === undefined && this.userId === undefined) { | ||||
|             // View initial page.
 | ||||
|             this.analyticsLogEvent('mod_wiki_view_wiki', { name: this.currentPageObj?.title }); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // Viewing a different subwiki.
 | ||||
|         const hasPersonalSubwikis = this.loadedSubwikis.some(subwiki => subwiki.userid > 0); | ||||
|         const hasGroupSubwikis = this.loadedSubwikis.some(subwiki => subwiki.groupid > 0); | ||||
| 
 | ||||
|         let url = `/mod/wiki/view.php?wid=${this.wiki.id}&title=${this.wiki.firstpagetitle}`; | ||||
|         if (hasPersonalSubwikis && hasGroupSubwikis) { | ||||
|             url += `&groupanduser=${this.groupId}-${this.userId}`; | ||||
|         } else if (hasPersonalSubwikis) { | ||||
|             url += `&uid=${this.userId}`; | ||||
|         } else { | ||||
|             url += `&group=${this.groupId}`; | ||||
|         } | ||||
| 
 | ||||
|         this.analyticsLogEvent('mod_wiki_view_wiki', { | ||||
|             name: this.currentPageObj?.title, | ||||
|             data: { subwiki: this.subwikiId, userid: this.userId, groupid: this.groupId }, | ||||
|             url, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Log page viewed. | ||||
|      * | ||||
|      * @param pageId Page ID. | ||||
|      */ | ||||
|     protected async logPageViewed(pageId: number): Promise<void> { | ||||
|         if (!this.wiki) { | ||||
|             return; // Shouldn't happen.
 | ||||
|         } | ||||
| 
 | ||||
|         await CoreUtils.ignoreErrors(AddonModWiki.logPageView(pageId, this.wiki.id)); | ||||
| 
 | ||||
|         this.analyticsLogEvent('mod_wiki_view_page', { | ||||
|             name: this.currentPageObj?.title, | ||||
|             data: { pageid: this.pageId }, | ||||
|             url: `/mod/wiki/view.php?page=${this.pageId}`, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -619,7 +665,9 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp | ||||
|                 homeView: this.getWikiHomeView(), | ||||
|                 moduleId: this.module.id, | ||||
|                 courseId: this.courseId, | ||||
|                 selectedId: this.currentPage, | ||||
|                 selectedTitle: this.currentPageObj && this.currentPageObj.title, | ||||
|                 wiki: this.wiki, | ||||
|             }, | ||||
|         }); | ||||
| 
 | ||||
|  | ||||
| @ -15,7 +15,8 @@ | ||||
| import { Component, Input, OnInit } from '@angular/core'; | ||||
| import { ModalController } from '@singletons'; | ||||
| import { AddonModWikiPageDBRecord } from '../../services/database/wiki'; | ||||
| import { AddonModWikiSubwikiPage } from '../../services/wiki'; | ||||
| import { AddonModWikiSubwikiPage, AddonModWikiWiki } from '../../services/wiki'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| 
 | ||||
| /** | ||||
|  * Modal to display the map of a Wiki. | ||||
| @ -27,6 +28,8 @@ import { AddonModWikiSubwikiPage } from '../../services/wiki'; | ||||
| export class AddonModWikiMapModalComponent implements OnInit { | ||||
| 
 | ||||
|     @Input() pages: (AddonModWikiSubwikiPage | AddonModWikiPageDBRecord)[] = []; | ||||
|     @Input() wiki?: AddonModWikiWiki; | ||||
|     @Input() selectedId?: number; | ||||
|     @Input() selectedTitle?: string; | ||||
|     @Input() moduleId?: number; | ||||
|     @Input() courseId?: number; | ||||
| @ -39,6 +42,16 @@ export class AddonModWikiMapModalComponent implements OnInit { | ||||
|      */ | ||||
|     ngOnInit(): void { | ||||
|         this.constructMap(); | ||||
| 
 | ||||
|         if (this.selectedId && this.wiki) { | ||||
|             CoreAnalytics.logEvent({ | ||||
|                 type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|                 ws: 'mod_wiki_get_subwiki_pages', | ||||
|                 name: this.selectedTitle || this.wiki.name, | ||||
|                 data: { id: this.wiki.id, pageid: this.selectedId, category: 'wiki' }, | ||||
|                 url: `/mod/wiki/map.php?pageid=${this.selectedId}`, | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -30,6 +30,7 @@ import { CoreForms } from '@singletons/form'; | ||||
| import { AddonModWiki, AddonModWikiProvider } from '../../services/wiki'; | ||||
| import { AddonModWikiOffline } from '../../services/wiki-offline'; | ||||
| import { AddonModWikiSync } from '../../services/wiki-sync'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that allows adding or editing a wiki page. | ||||
| @ -64,6 +65,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { | ||||
|     protected editOffline = false; // Whether the user is editing an offline page.
 | ||||
|     protected subwikiFiles: CoreWSFile[] = []; // List of files of the subwiki.
 | ||||
|     protected originalContent?: string; // The original page content.
 | ||||
|     protected originalTitle?: string; // The original page title.
 | ||||
|     protected version?: number; // Page version.
 | ||||
|     protected renewLockInterval?: number; // An interval to renew the lock every certain time.
 | ||||
|     protected forceLeave = false; // To allow leaving the page without checking for changes.
 | ||||
| @ -89,17 +91,17 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { | ||||
|         this.groupId = CoreNavigator.getRouteNumberParam('groupId'); | ||||
|         this.userId = CoreNavigator.getRouteNumberParam('userId'); | ||||
| 
 | ||||
|         let pageTitle = CoreNavigator.getRouteParam<string>('pageTitle'); | ||||
|         pageTitle = pageTitle ? CoreTextUtils.cleanTags(pageTitle.replace(/\+/g, ' '), { singleLine: true }) : ''; | ||||
|         const pageTitle = CoreNavigator.getRouteParam<string>('pageTitle'); | ||||
|         this.originalTitle = pageTitle ? CoreTextUtils.cleanTags(pageTitle.replace(/\+/g, ' '), { singleLine: true }) : ''; | ||||
| 
 | ||||
|         this.canEditTitle = !pageTitle; | ||||
|         this.title = pageTitle ? | ||||
|             Translate.instant('addon.mod_wiki.editingpage', { $a: pageTitle }) : | ||||
|         this.canEditTitle = !this.originalTitle; | ||||
|         this.title = this.originalTitle ? | ||||
|             Translate.instant('addon.mod_wiki.editingpage', { $a: this.originalTitle }) : | ||||
|             Translate.instant('addon.mod_wiki.newpagehdr'); | ||||
|         this.blockId = AddonModWikiSync.getSubwikiBlockId(this.subwikiId, this.wikiId, this.userId, this.groupId); | ||||
| 
 | ||||
|         // Create the form group and its controls.
 | ||||
|         this.pageForm.addControl('title', this.formBuilder.control(pageTitle)); | ||||
|         this.pageForm.addControl('title', this.formBuilder.control(this.originalTitle)); | ||||
|         this.pageForm.addControl('text', this.contentControl); | ||||
| 
 | ||||
|         // Block the wiki so it cannot be synced.
 | ||||
| @ -111,8 +113,8 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { | ||||
|             if (this.section) { | ||||
|                 this.editorExtraParams.section = this.section; | ||||
|             } | ||||
|         } else if (pageTitle) { | ||||
|             this.editorExtraParams.pagetitle = pageTitle; | ||||
|         } else if (this.originalTitle) { | ||||
|             this.editorExtraParams.pagetitle = this.originalTitle; | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
| @ -126,6 +128,8 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { | ||||
|                     this.blockId = newBlockId; | ||||
|                     CoreSync.blockOperation(this.component, this.blockId); | ||||
|                 } | ||||
| 
 | ||||
|                 this.logView(); | ||||
|             } | ||||
|         } finally { | ||||
|             this.loaded = true; | ||||
| @ -158,6 +162,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { | ||||
|                 this.wikiId = pageContents.wikiid; | ||||
|                 this.subwikiId = pageContents.subwikiid; | ||||
|                 this.title = Translate.instant('addon.mod_wiki.editingpage', { $a: pageContents.title }); | ||||
|                 this.originalTitle = pageContents.title; | ||||
|                 this.groupId = pageContents.groupid; | ||||
|                 this.userId = pageContents.userid; | ||||
|                 canEdit = pageContents.caneditpage; | ||||
| @ -466,6 +471,34 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Log view. | ||||
|      */ | ||||
|     protected logView(): void { | ||||
|         let url: string; | ||||
|         if (this.pageId) { | ||||
|             url = `/mod/wiki/edit.php?pageid=${this.pageId}` + | ||||
|                 (this.section ? `§ion=${this.section.replace(/ /g, '+')}` : ''); | ||||
|         } else if (this.originalTitle) { | ||||
|             const title = this.originalTitle.replace(/ /g, '+'); | ||||
|             if (this.subwikiId) { | ||||
|                 url = `/mod/wiki/create.php?swid=${this.subwikiId}&title=${title}&action=new`; | ||||
|             } else { | ||||
|                 url = `/mod/wiki/create.php?wid=${this.wikiId}&group=${this.groupId ?? 0}&uid=${this.userId ?? 0}&title=${title}`; | ||||
|             } | ||||
|         } else { | ||||
|             url = `/mod/wiki/create.php?action=new&wid=${this.wikiId}&swid=${this.subwikiId}`; | ||||
|         } | ||||
| 
 | ||||
|         CoreAnalytics.logEvent({ | ||||
|             type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|             ws: this.pageId ? 'mod_wiki_edit_page' : 'mod_wiki_new_page', | ||||
|             name: this.originalTitle ?? Translate.instant('addon.mod_wiki.newpagehdr'), | ||||
|             data: { id: this.wikiId, subwiki: this.subwikiId, category: 'wiki' }, | ||||
|             url, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|  | ||||
| @ -621,23 +621,19 @@ export class AddonModWikiProvider { | ||||
|      * | ||||
|      * @param id Page ID. | ||||
|      * @param wikiId Wiki ID. | ||||
|      * @param name Name of the wiki. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @returns Promise resolved when the WS call is successful. | ||||
|      */ | ||||
|     logPageView(id: number, wikiId: number, name?: string, siteId?: string): Promise<void> { | ||||
|     logPageView(id: number, wikiId: number, siteId?: string): Promise<void> { | ||||
|         const params: AddonModWikiViewPageWSParams = { | ||||
|             pageid: id, | ||||
|         }; | ||||
| 
 | ||||
|         return CoreCourseLogHelper.logSingle( | ||||
|         return CoreCourseLogHelper.log( | ||||
|             'mod_wiki_view_page', | ||||
|             params, | ||||
|             AddonModWikiProvider.COMPONENT, | ||||
|             wikiId, | ||||
|             name, | ||||
|             'wiki', | ||||
|             params, | ||||
|             siteId, | ||||
|         ); | ||||
|     } | ||||
| @ -646,23 +642,19 @@ export class AddonModWikiProvider { | ||||
|      * Report the wiki as being viewed. | ||||
|      * | ||||
|      * @param id Wiki ID. | ||||
|      * @param name Name of the wiki. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @returns Promise resolved when the WS call is successful. | ||||
|      */ | ||||
|     logView(id: number, name?: string, siteId?: string): Promise<void> { | ||||
|     logView(id: number, siteId?: string): Promise<void> { | ||||
|         const params: AddonModWikiViewWikiWSParams = { | ||||
|             wikiid: id, | ||||
|         }; | ||||
| 
 | ||||
|         return CoreCourseLogHelper.logSingle( | ||||
|         return CoreCourseLogHelper.log( | ||||
|             'mod_wiki_view_wiki', | ||||
|             params, | ||||
|             AddonModWikiProvider.COMPONENT, | ||||
|             id, | ||||
|             name, | ||||
|             'wiki', | ||||
|             {}, | ||||
|             siteId, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
| @ -66,7 +66,7 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity | ||||
|     @Input() group = 0; | ||||
| 
 | ||||
|     component = AddonModWorkshopProvider.COMPONENT; | ||||
|     moduleName = 'workshop'; | ||||
|     pluginName = 'workshop'; | ||||
| 
 | ||||
|     workshop?: AddonModWorkshopData; | ||||
|     page = 0; | ||||
| @ -255,7 +255,9 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity | ||||
|             return; // Shouldn't happen.
 | ||||
|         } | ||||
| 
 | ||||
|         await AddonModWorkshop.logView(this.workshop.id, this.workshop.name); | ||||
|         await CoreUtils.ignoreErrors(AddonModWorkshop.logView(this.workshop.id)); | ||||
| 
 | ||||
|         this.analyticsLogEvent('mod_workshop_view_workshop'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -39,6 +39,8 @@ import { | ||||
| import { AddonModWorkshopHelper, AddonModWorkshopSubmissionAssessmentWithFormData } from '../../services/workshop-helper'; | ||||
| import { AddonModWorkshopOffline } from '../../services/workshop-offline'; | ||||
| import { AddonModWorkshopSyncProvider } from '../../services/workshop-sync'; | ||||
| import { CoreTime } from '@singletons/time'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays a workshop assessment. | ||||
| @ -89,6 +91,7 @@ export class AddonModWorkshopAssessmentPage implements OnInit, OnDestroy, CanLea | ||||
|     protected siteId: string; | ||||
|     protected currentUserId: number; | ||||
|     protected forceLeave = false; | ||||
|     protected logView: () => void; | ||||
| 
 | ||||
|     constructor( | ||||
|         protected fb: FormBuilder, | ||||
| @ -111,6 +114,20 @@ export class AddonModWorkshopAssessmentPage implements OnInit, OnDestroy, CanLea | ||||
|                 this.refreshAllData(); | ||||
|             } | ||||
|         }, this.siteId); | ||||
| 
 | ||||
|         this.logView = CoreTime.once(async () => { | ||||
|             if (!this.workshop) { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             CoreAnalytics.logEvent({ | ||||
|                 type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|                 ws: 'mod_workshop_get_assessment', | ||||
|                 name: this.workshop.name, | ||||
|                 data: { id: this.workshop.id, assessmentid: this.assessment.id, category: 'workshop' }, | ||||
|                 url: `/mod/workshop/assessment.php?asid=${this.assessment.id}`, | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -40,6 +40,7 @@ import { | ||||
| } from '../../services/workshop'; | ||||
| import { AddonModWorkshopHelper, AddonModWorkshopSubmissionDataWithOfflineData } from '../../services/workshop-helper'; | ||||
| import { AddonModWorkshopOffline } from '../../services/workshop-offline'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays the workshop edit submission. | ||||
| @ -224,6 +225,8 @@ export class AddonModWorkshopEditSubmissionPage implements OnInit, OnDestroy, Ca | ||||
|             } | ||||
| 
 | ||||
|             this.loaded = true; | ||||
| 
 | ||||
|             this.logView(); | ||||
|         } catch (error) { | ||||
|             this.loaded = false; | ||||
| 
 | ||||
| @ -233,6 +236,23 @@ export class AddonModWorkshopEditSubmissionPage implements OnInit, OnDestroy, Ca | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Log view. | ||||
|      */ | ||||
|     protected logView(): void { | ||||
|         if (!this.workshop) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         CoreAnalytics.logEvent({ | ||||
|             type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|             ws: this.editing ? 'mod_workshop_update_submission' : 'mod_workshop_add_submission', | ||||
|             name: this.workshop.name, | ||||
|             data: { id: this.workshop.id, submissionid: this.submissionId, category: 'workshop' }, | ||||
|             url: `/mod/workshop/submission.php?cmid=${this.module.id}&id=${this.submissionId}&edit=on`, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Force leaving the page, without checking for changes. | ||||
|      */ | ||||
|  | ||||
| @ -47,6 +47,8 @@ import { | ||||
| } from '../../services/workshop-helper'; | ||||
| import { AddonModWorkshopOffline } from '../../services/workshop-offline'; | ||||
| import { AddonModWorkshopSyncProvider, AddonModWorkshopAutoSyncData } from '../../services/workshop-sync'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| import { CoreTime } from '@singletons/time'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays a workshop submission. | ||||
| @ -102,7 +104,7 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy, CanLea | ||||
|     protected obsAssessmentSaved: CoreEventObserver; | ||||
|     protected syncObserver: CoreEventObserver; | ||||
|     protected isDestroyed = false; | ||||
|     protected fetchSuccess = false; | ||||
|     protected logView: () => void; | ||||
| 
 | ||||
|     constructor( | ||||
|         protected fb: FormBuilder, | ||||
| @ -125,6 +127,8 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy, CanLea | ||||
|             // Update just when all database is synced.
 | ||||
|             this.eventReceived(data); | ||||
|         }, this.siteId); | ||||
| 
 | ||||
|         this.logView = CoreTime.once(() => this.performLogView()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -599,19 +603,21 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy, CanLea | ||||
|     /** | ||||
|      * Log submission viewed. | ||||
|      */ | ||||
|     protected async logView(): Promise<void> { | ||||
|         if (this.fetchSuccess) { | ||||
|             return; // Already done.
 | ||||
|         } | ||||
| 
 | ||||
|         this.fetchSuccess = true; | ||||
| 
 | ||||
|     protected async performLogView(): Promise<void> { | ||||
|         try { | ||||
|             await AddonModWorkshop.logViewSubmission(this.submissionId, this.workshopId, this.workshop.name); | ||||
|             await AddonModWorkshop.logViewSubmission(this.submissionId, this.workshopId); | ||||
|             CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata); | ||||
|         } catch { | ||||
|             // Ignore errors.
 | ||||
|         } | ||||
| 
 | ||||
|         CoreAnalytics.logEvent({ | ||||
|             type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|             ws: 'mod_workshop_view_submission', | ||||
|             name: this.workshop.name, | ||||
|             data: { id: this.workshop.id, submissionid: this.submissionId, category: 'workshop' }, | ||||
|             url: `/mod/workshop/submission.php?cmid=${this.module.id}&id=${this.submissionId}`, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -1443,23 +1443,19 @@ export class AddonModWorkshopProvider { | ||||
|      * Report the workshop as being viewed. | ||||
|      * | ||||
|      * @param id Workshop ID. | ||||
|      * @param name Name of the workshop. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @returns Promise resolved when the WS call is successful. | ||||
|      */ | ||||
|     async logView(id: number, name?: string, siteId?: string): Promise<void> { | ||||
|     async logView(id: number, siteId?: string): Promise<void> { | ||||
|         const params: AddonModWorkshopViewWorkshopWSParams = { | ||||
|             workshopid: id, | ||||
|         }; | ||||
| 
 | ||||
|         await CoreCourseLogHelper.logSingle( | ||||
|         await CoreCourseLogHelper.log( | ||||
|             'mod_workshop_view_workshop', | ||||
|             params, | ||||
|             AddonModWorkshopProvider.COMPONENT, | ||||
|             id, | ||||
|             name, | ||||
|             'workshop', | ||||
|             {}, | ||||
|             siteId, | ||||
|         ); | ||||
|     } | ||||
| @ -1469,23 +1465,19 @@ export class AddonModWorkshopProvider { | ||||
|      * | ||||
|      * @param id Submission ID. | ||||
|      * @param workshopId Workshop ID. | ||||
|      * @param name Name of the workshop. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @returns Promise resolved when the WS call is successful. | ||||
|      */ | ||||
|     async logViewSubmission(id: number, workshopId: number, name?: string, siteId?: string): Promise<void> { | ||||
|     async logViewSubmission(id: number, workshopId: number, siteId?: string): Promise<void> { | ||||
|         const params: AddonModWorkshopViewSubmissionWSParams = { | ||||
|             submissionid: id, | ||||
|         }; | ||||
| 
 | ||||
|         await CoreCourseLogHelper.logSingle( | ||||
|         await CoreCourseLogHelper.log( | ||||
|             'mod_workshop_view_submission', | ||||
|             params, | ||||
|             AddonModWorkshopProvider.COMPONENT, | ||||
|             workshopId, | ||||
|             name, | ||||
|             'workshop', | ||||
|             params, | ||||
|             siteId, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
| @ -21,12 +21,16 @@ import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; | ||||
| import { CoreAnimations } from '@components/animations'; | ||||
| import { CoreUser, CoreUserProfile } from '@features/user/services/user'; | ||||
| import { IonContent, IonRefresher } from '@ionic/angular'; | ||||
| import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; | ||||
| import { CoreNavigator } from '@services/navigator'; | ||||
| import { CoreSites } from '@services/sites'; | ||||
| import { CoreDomUtils, ToastDuration } from '@services/utils/dom'; | ||||
| import { CoreTextUtils } from '@services/utils/text'; | ||||
| import { CoreUrlUtils } from '@services/utils/url'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| import { Translate } from '@singletons'; | ||||
| import { CoreEventObserver, CoreEvents } from '@singletons/events'; | ||||
| import { CoreTime } from '@singletons/time'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays a list of notes. | ||||
| @ -54,9 +58,11 @@ export class AddonNotesListPage implements OnInit, OnDestroy { | ||||
|     currentUserId!: number; | ||||
| 
 | ||||
|     protected syncObserver!: CoreEventObserver; | ||||
|     protected logAfterFetch = true; | ||||
|     protected logView: () => void; | ||||
| 
 | ||||
|     constructor() { | ||||
|         this.logView = CoreTime.once(() => this.performLogView()); | ||||
| 
 | ||||
|         try { | ||||
|             this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId'); | ||||
|             this.userId = CoreNavigator.getRouteNumberParam('userId'); | ||||
| @ -128,10 +134,7 @@ export class AddonNotesListPage implements OnInit, OnDestroy { | ||||
|                 this.notes = await AddonNotes.getNotesUserData(notesList); | ||||
|             } | ||||
| 
 | ||||
|             if (this.logAfterFetch) { | ||||
|                 this.logAfterFetch = false; | ||||
|                 CoreUtils.ignoreErrors(AddonNotes.logView(this.courseId, this.userId)); | ||||
|             } | ||||
|             this.logView(); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModal(error); | ||||
|         } finally { | ||||
| @ -176,7 +179,6 @@ export class AddonNotesListPage implements OnInit, OnDestroy { | ||||
|         this.notesLoaded = false; | ||||
|         this.refreshIcon = CoreConstants.ICON_LOADING; | ||||
|         this.syncIcon = CoreConstants.ICON_LOADING; | ||||
|         this.logAfterFetch = true; | ||||
| 
 | ||||
|         await this.fetchNotes(true); | ||||
|     } | ||||
| @ -190,6 +192,8 @@ export class AddonNotesListPage implements OnInit, OnDestroy { | ||||
|         e.preventDefault(); | ||||
|         e.stopPropagation(); | ||||
| 
 | ||||
|         this.logViewAdd(); | ||||
| 
 | ||||
|         const modalData = await CoreDomUtils.openModal<AddonNotesAddModalReturn>({ | ||||
|             component: AddonNotesAddComponent, | ||||
|             componentProps: { | ||||
| @ -225,6 +229,8 @@ export class AddonNotesListPage implements OnInit, OnDestroy { | ||||
|         e.stopPropagation(); | ||||
| 
 | ||||
|         try { | ||||
|             this.logViewDelete(note); | ||||
| 
 | ||||
|             await CoreDomUtils.showDeleteConfirm('addon.notes.deleteconfirm'); | ||||
|             try { | ||||
|                 await AddonNotes.deleteNote(note, this.courseId); | ||||
| @ -294,6 +300,58 @@ export class AddonNotesListPage implements OnInit, OnDestroy { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Log view. | ||||
|      */ | ||||
|     protected async performLogView(): Promise<void> { | ||||
|         await CoreUtils.ignoreErrors(AddonNotes.logView(this.courseId, this.userId)); | ||||
| 
 | ||||
|         CoreAnalytics.logEvent({ | ||||
|             type: CoreAnalyticsEventType.VIEW_ITEM_LIST, | ||||
|             ws: 'core_notes_view_notes', | ||||
|             name: Translate.instant('addon.notes.notes'), | ||||
|             data: { courseid: this.courseId, userid: this.userId || 0, category: 'notes' }, | ||||
|             url: CoreUrlUtils.addParamsToUrl('/notes/index.php', { | ||||
|                 user: this.userId, | ||||
|                 course: this.courseId !== CoreSites.getCurrentSiteHomeId() ? this.courseId : undefined, | ||||
|             }), | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Log view. | ||||
|      */ | ||||
|     protected async logViewAdd(): Promise<void> { | ||||
|         CoreAnalytics.logEvent({ | ||||
|             type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|             ws: 'core_notes_create_notes', | ||||
|             name: Translate.instant('addon.notes.notes'), | ||||
|             data: { courseid: this.courseId, userid: this.userId || 0, category: 'notes' }, | ||||
|             url: CoreUrlUtils.addParamsToUrl('/notes/edit.php', { | ||||
|                 courseid: this.courseId, | ||||
|                 userid: this.userId, | ||||
|                 publishstate: this.type === 'personal' ? 'draft' : (this.type === 'course' ? 'public' : 'site'), | ||||
|             }), | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Log view. | ||||
|      */ | ||||
|     protected async logViewDelete(note: AddonNotesNoteFormatted): Promise<void> { | ||||
|         if (!note.id) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         CoreAnalytics.logEvent({ | ||||
|             type: CoreAnalyticsEventType.VIEW_ITEM, | ||||
|             ws: 'core_notes_delete_notes', | ||||
|             name: Translate.instant('addon.notes.notes'), | ||||
|             data: { id: note.id, category: 'notes' }, | ||||
|             url: `/notes/delete.php?id=${note.id}`, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Page destroyed. | ||||
|      */ | ||||
|  | ||||
| @ -15,7 +15,6 @@ | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { CoreWSError } from '@classes/errors/wserror'; | ||||
| import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; | ||||
| import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications'; | ||||
| import { CoreUser } from '@features/user/services/user'; | ||||
| import { CoreNetwork } from '@services/network'; | ||||
| import { CoreSites } from '@services/sites'; | ||||
| @ -414,8 +413,6 @@ export class AddonNotesProvider { | ||||
|             userid: userId || 0, | ||||
|         }; | ||||
| 
 | ||||
|         CorePushNotifications.logViewListEvent('notes', 'core_notes_view_notes', params, site.getId()); | ||||
| 
 | ||||
|         await site.write('core_notes_view_notes', params); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user