diff --git a/src/addons/addons.module.ts b/src/addons/addons.module.ts index 6686059a2..82761f055 100644 --- a/src/addons/addons.module.ts +++ b/src/addons/addons.module.ts @@ -39,9 +39,11 @@ import { AddonBlockTagsModule } from './block/tags/tags.module'; import { AddonPrivateFilesModule } from './privatefiles/privatefiles.module'; import { AddonFilterModule } from './filter/filter.module'; import { AddonUserProfileFieldModule } from './userprofilefield/userprofilefield.module'; +import { AddonBadgesModule } from './badges/badges.module'; @NgModule({ imports: [ + AddonBadgesModule, AddonPrivateFilesModule, AddonFilterModule, AddonBlockActivityResultsModule, diff --git a/src/addons/badges/badges-lazy.module.ts b/src/addons/badges/badges-lazy.module.ts new file mode 100644 index 000000000..e1aee5b89 --- /dev/null +++ b/src/addons/badges/badges-lazy.module.ts @@ -0,0 +1,37 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +const routes: Routes = [ + { + path: '', + redirectTo: 'user', + pathMatch: 'full', + }, + { + path: 'issue', + loadChildren: () => import('./pages/issued-badge/issued-badge.module').then( m => m.AddonBadgesIssuedBadgePageModule), + }, + { + path: 'user', + loadChildren: () => import('./pages/user-badges/user-badges.module').then( m => m.AddonBadgesUserBadgesPageModule), + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], +}) +export class AddonBadgesLazyModule {} diff --git a/src/addons/badges/badges.module.ts b/src/addons/badges/badges.module.ts new file mode 100644 index 000000000..b0b192970 --- /dev/null +++ b/src/addons/badges/badges.module.ts @@ -0,0 +1,54 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { APP_INITIALIZER, NgModule } from '@angular/core'; +import { Routes } from '@angular/router'; + +import { AddonBadgesMyBadgesLinkHandler } from './services/handlers/mybadges-link'; +import { AddonBadgesBadgeLinkHandler } from './services/handlers/badge-link'; +import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate'; +import { CoreUserDelegate } from '@features/user/services/user-delegate'; +import { AddonBadgesUserHandler } from './services/handlers/user'; +import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module'; +// @todo import { CorePushNotificationsDelegate } from '@core/pushnotifications/services/delegate'; +// import { AddonBadgesPushClickHandler } from './services/push-click-handler'; + +const mainMenuHomeSiblingRoutes: Routes = [ + { + path: 'badges', + loadChildren: () => import('./badges-lazy.module').then(m => m.AddonBadgesLazyModule), + }, +]; + +@NgModule({ + imports: [ + CoreMainMenuTabRoutingModule.forChild({ + siblings: mainMenuHomeSiblingRoutes, + }), + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [], + useFactory: () => () => { + CoreContentLinksDelegate.instance.registerHandler(AddonBadgesMyBadgesLinkHandler.instance); + CoreContentLinksDelegate.instance.registerHandler(AddonBadgesBadgeLinkHandler.instance); + CoreUserDelegate.instance.registerHandler(AddonBadgesUserHandler.instance); + // CorePushNotificationsDelegate.instance.registerHandler(AddonBadgesPushClickHandler.instance); + }, + }, + ], +}) +export class AddonBadgesModule {} diff --git a/src/addons/badges/lang.json b/src/addons/badges/lang.json new file mode 100644 index 000000000..6c8a2b856 --- /dev/null +++ b/src/addons/badges/lang.json @@ -0,0 +1,29 @@ +{ + "alignment": "Alignment", + "badgedetails": "Badge details", + "badges": "Badges", + "bendorsement": "Endorsement", + "claimcomment": "Endorsement comment", + "claimid": "Claim URL", + "contact": "Contact", + "dateawarded": "Date issued", + "expired": "Expired", + "expirydate": "Expiry date", + "imageauthoremail": "Image author's email", + "imageauthorname": "Image author's name", + "imageauthorurl": "Image author's URL", + "imagecaption": "Image caption", + "issuancedetails": "Badge expiry", + "issuerdetails": "Issuer details", + "issueremail": "Email", + "issuername": "Issuer name", + "issuerurl": "Issuer URL", + "language": "Language", + "noalignment": "This badge does not have any external skills or standards specified.", + "nobadges": "There are no badges available.", + "norelated": "This badge does not have any related badges.", + "recipientdetails": "Recipient details", + "relatedbages": "Related badges", + "version": "Version", + "warnexpired": "(This badge has expired!)" +} \ No newline at end of file diff --git a/src/addons/badges/pages/issued-badge/issued-badge.html b/src/addons/badges/pages/issued-badge/issued-badge.html new file mode 100644 index 000000000..545ca1fc2 --- /dev/null +++ b/src/addons/badges/pages/issued-badge/issued-badge.html @@ -0,0 +1,228 @@ + + + + + + {{ badge.name }} + {{ 'addon.badges.badges' | translate }} + + + + + + + + + + + + + {{ 'addon.badges.expired' | translate }} + + + + + + + + +

{{ 'addon.badges.recipientdetails' | translate}}

+
+
+ + +

{{ 'core.name' | translate}}

+

{{ user.fullname }}

+
+
+
+ + + + + +

{{ 'addon.badges.issuerdetails' | translate}}

+
+
+ + +

{{ 'addon.badges.issuername' | translate}}

+

{{ badge.issuername }}

+
+
+ + +

{{ 'addon.badges.contact' | translate}}

+

{{ badge.issuercontact }}

+
+
+
+ + + + +

{{ 'addon.badges.badgedetails' | translate}}

+
+
+ + +

{{ 'core.name' | translate}}

+

{{ badge.name }}

+
+
+ + +

{{ 'addon.badges.version' | translate}}

+

{{ badge.version }}

+
+
+ + +

{{ 'addon.badges.language' | translate}}

+

{{ badge.language }}

+
+
+ + +

{{ 'core.description' | translate}}

+

{{ badge.description }}

+
+
+ + +

{{ 'addon.badges.imageauthorname' | translate}}

+

{{ badge.imageauthorname }}

+
+
+ + +

{{ 'addon.badges.imageauthoremail' | translate}}

+

{{ badge.imageauthoremail }}

+
+
+ + +

{{ 'addon.badges.imageauthorurl' | translate}}

+

{{ badge.imageauthorurl }}

+
+
+ + +

{{ 'addon.badges.imagecaption' | translate}}

+

{{ badge.imagecaption }}

+
+
+ + +

{{ 'core.course' | translate}}

+

+ + +

+
+
+ +
+ + + + +

{{ 'addon.badges.issuancedetails' | translate}}

+
+
+ + +

{{ 'addon.badges.dateawarded' | translate}}

+

{{badge.dateissued * 1000 | coreFormatDate }}

+
+
+ + +

{{ 'addon.badges.expirydate' | translate}}

+

+ {{ badge.dateexpire * 1000 | coreFormatDate }} + + {{ 'addon.badges.warnexpired' | translate }} + +

+
+
+ +
+ + + + +

{{ 'addon.badges.bendorsement' | translate}}

+
+ + +

{{ 'addon.badges.issuername' | translate}}

+

{{ badge.endorsement.issuername }}

+
+
+ + +

{{ 'addon.badges.issueremail' | translate}}

+

+ + {{ badge.endorsement.issueremail }} + +

+
+
+ + +

{{ 'addon.badges.issuerurl' | translate}}

+

{{ badge.endorsement.issuerurl }}

+
+
+ + +

{{ 'addon.badges.dateawarded' | translate}}

+

{{ badge.endorsement.dateissued * 1000 | coreFormatDate }}

+
+
+ + +

{{ 'addon.badges.claimid' | translate}}

+

{{ badge.endorsement.claimid }}

+
+
+ + +

{{ 'addon.badges.claimcomment' | translate}}

+

{{ badge.endorsement.claimcomment }}

+
+
+
+ + + + +

{{ 'addon.badges.relatedbages' | translate}}

+
+ +

{{ relatedBadge.name }}

+
+ +

{{ 'addon.badges.norelated' | translate}}

+
+
+ + + + +

{{ 'addon.badges.alignment' | translate}}

+
+ +

{{ alignment.targetname }}

+
+ +

{{ 'addon.badges.noalignment' | translate}}

+
+
+
+
+
diff --git a/src/addons/badges/pages/issued-badge/issued-badge.module.ts b/src/addons/badges/pages/issued-badge/issued-badge.module.ts new file mode 100644 index 000000000..e29cbe77d --- /dev/null +++ b/src/addons/badges/pages/issued-badge/issued-badge.module.ts @@ -0,0 +1,49 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { IonicModule } from '@ionic/angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { RouterModule, Routes } from '@angular/router'; +import { CommonModule } from '@angular/common'; + +import { CoreComponentsModule } from '@components/components.module'; +import { CoreDirectivesModule } from '@directives/directives.module'; +import { CorePipesModule } from '@pipes/pipes.module'; + +import { AddonBadgesIssuedBadgePage } from './issued-badge.page'; + +const routes: Routes = [ + { + path: '', + component: AddonBadgesIssuedBadgePage, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + CommonModule, + IonicModule, + TranslateModule.forChild(), + CoreComponentsModule, + CoreDirectivesModule, + CorePipesModule, + ], + declarations: [ + AddonBadgesIssuedBadgePage, + ], + exports: [RouterModule], +}) +export class AddonBadgesIssuedBadgePageModule {} diff --git a/src/addons/badges/pages/issued-badge/issued-badge.page.ts b/src/addons/badges/pages/issued-badge/issued-badge.page.ts new file mode 100644 index 000000000..70f90e8b2 --- /dev/null +++ b/src/addons/badges/pages/issued-badge/issued-badge.page.ts @@ -0,0 +1,112 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, OnInit } from '@angular/core'; +import { IonRefresher } from '@ionic/angular'; +import { CoreTimeUtils } from '@services/utils/time'; +import { CoreDomUtils } from '@services/utils/dom'; +import { CoreSites } from '@services/sites'; +import { CoreUser, CoreUserProfile } from '@features/user/services/user'; +import { AddonBadges, AddonBadgesUserBadge } from '../../services/badges'; +import { CoreUtils } from '@services/utils/utils'; +import { ActivatedRoute } from '@angular/router'; +import { CoreCourses, CoreEnrolledCourseData } from '@features/courses/services/courses'; + +/** + * Page that displays the list of calendar events. + */ +@Component({ + selector: 'page-addon-badges-issued-badge', + templateUrl: 'issued-badge.html', +}) +export class AddonBadgesIssuedBadgePage implements OnInit { + + protected badgeHash = ''; + protected userId!: number; + + courseId = 0; + user?: CoreUserProfile; + course?: CoreEnrolledCourseData; + badge?: AddonBadgesUserBadge; + badgeLoaded = false; + currentTime = 0; + + constructor( + protected route: ActivatedRoute, + ) { } + + /** + * View loaded. + */ + ngOnInit(): void { + this.courseId = this.route.snapshot.queryParams['courseId'] || this.courseId; // Use 0 for site badges. + this.userId = this.route.snapshot.queryParams['userId'] || + CoreSites.instance.getCurrentSite()?.getUserId(); + this.badgeHash = this.route.snapshot.queryParams['badgeHash']; + + this.fetchIssuedBadge().finally(() => { + this.badgeLoaded = true; + }); + } + + /** + * Fetch the issued badge required for the view. + * + * @return Promise resolved when done. + */ + async fetchIssuedBadge(): Promise { + this.currentTime = CoreTimeUtils.instance.timestamp(); + + this.user = await CoreUser.instance.getProfile(this.userId, this.courseId, true); + + try { + const badges = await AddonBadges.instance.getUserBadges(this.courseId, this.userId); + const badge = badges.find((badge) => this.badgeHash == badge.uniquehash); + + if (!badge) { + return; + } + + this.badge = badge; + if (badge.courseid) { + try { + this.course = await CoreCourses.instance.getUserCourse(badge.courseid, true); + } catch { + // Maybe an old deleted course. + this.course = undefined; + } + } + } catch (message) { + CoreDomUtils.instance.showErrorModalDefault(message, 'Error getting badge data.'); + } + } + + /** + * Refresh the badges. + * + * @param refresher Refresher. + */ + async refreshBadges(refresher?: CustomEvent): Promise { + await CoreUtils.instance.ignoreErrors(Promise.all([ + AddonBadges.instance.invalidateUserBadges(this.courseId, this.userId), + ])); + + await CoreUtils.instance.ignoreErrors(Promise.all([ + this.fetchIssuedBadge(), + ])); + + refresher?.detail.complete(); + } + +} diff --git a/src/addons/badges/pages/user-badges/user-badges.html b/src/addons/badges/pages/user-badges/user-badges.html new file mode 100644 index 000000000..355c64698 --- /dev/null +++ b/src/addons/badges/pages/user-badges/user-badges.html @@ -0,0 +1,34 @@ + + + + + + {{ 'addon.badges.badges' | translate }} + + + + + + + + + + + + + + + + + +

{{ badge.name }}

+

{{ badge.dateissued * 1000 | coreFormatDate :'strftimedatetimeshort' }}

+
+ + {{ 'addon.badges.expired' | translate }} + +
+
+
+
diff --git a/src/addons/badges/pages/user-badges/user-badges.module.ts b/src/addons/badges/pages/user-badges/user-badges.module.ts new file mode 100644 index 000000000..92a9b6978 --- /dev/null +++ b/src/addons/badges/pages/user-badges/user-badges.module.ts @@ -0,0 +1,49 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { IonicModule } from '@ionic/angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { RouterModule, Routes } from '@angular/router'; +import { CommonModule } from '@angular/common'; + +import { CoreComponentsModule } from '@components/components.module'; +import { CoreDirectivesModule } from '@directives/directives.module'; +import { CorePipesModule } from '@pipes/pipes.module'; + +import { AddonBadgesUserBadgesPage } from './user-badges.page'; + +const routes: Routes = [ + { + path: '', + component: AddonBadgesUserBadgesPage, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + CommonModule, + IonicModule, + TranslateModule.forChild(), + CoreComponentsModule, + CoreDirectivesModule, + CorePipesModule, + ], + declarations: [ + AddonBadgesUserBadgesPage, + ], + exports: [RouterModule], +}) +export class AddonBadgesUserBadgesPageModule {} diff --git a/src/addons/badges/pages/user-badges/user-badges.page.ts b/src/addons/badges/pages/user-badges/user-badges.page.ts new file mode 100644 index 000000000..d1ea67af6 --- /dev/null +++ b/src/addons/badges/pages/user-badges/user-badges.page.ts @@ -0,0 +1,113 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, OnInit } from '@angular/core'; +import { IonRefresher } from '@ionic/angular'; +import { AddonBadges, AddonBadgesUserBadge } from '../../services/badges'; +import { CoreTimeUtils } from '@services/utils/time'; +import { CoreDomUtils } from '@services/utils/dom'; +import { CoreSites } from '@services/sites'; +import { CoreUtils } from '@services/utils/utils'; +import { CoreNavHelper } from '@services/nav-helper'; +import { ActivatedRoute } from '@angular/router'; +// @todo import { CoreSplitViewComponent } from '@components/split-view/split-view'; + +/** + * Page that displays the list of calendar events. + */ +@Component({ + selector: 'page-addon-badges-user-badges', + templateUrl: 'user-badges.html', +}) +export class AddonBadgesUserBadgesPage implements OnInit { + + // @ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent; + + courseId = 0; + userId!: number; + + badgesLoaded = false; + badges: AddonBadgesUserBadge[] = []; + currentTime = 0; + badgeHash!: string; + + constructor( + protected route: ActivatedRoute, + ) { } + + /** + * View loaded. + */ + ngOnInit(): void { + + this.courseId = this.route.snapshot.queryParams['courseId'] || this.courseId; // Use 0 for site badges. + this.userId = this.route.snapshot.queryParams['userId'] || + CoreSites.instance.getCurrentSite()?.getUserId(); + + this.fetchBadges().finally(() => { + // @todo splitview + /* if (!this.badgeHash && this.splitviewCtrl.isOn() && this.badges.length > 0) { + // Take first and load it. + this.loadIssuedBadge(this.badges[0].uniquehash); + }*/ + this.badgesLoaded = true; + }); + } + + /** + * Fetch all the badges required for the view. + * + * @return Promise resolved when done. + */ + async fetchBadges(): Promise { + this.currentTime = CoreTimeUtils.instance.timestamp(); + + try { + this.badges = await AddonBadges.instance.getUserBadges(this.courseId, this.userId); + } catch (message) { + CoreDomUtils.instance.showErrorModalDefault(message, 'Error getting badges data.'); + } + } + + /** + * Refresh the badges. + * + * @param refresher Refresher. + */ + async refreshBadges(refresher?: CustomEvent): Promise { + await CoreUtils.instance.ignoreErrors(Promise.all([ + AddonBadges.instance.invalidateUserBadges(this.courseId, this.userId), + ])); + + await CoreUtils.instance.ignoreErrors(Promise.all([ + this.fetchBadges(), + ])); + + refresher?.detail.complete(); + } + + /** + * Navigate to a particular badge. + * + * @param badgeHash Badge to load. + */ + loadIssuedBadge(badgeHash: string): void { + this.badgeHash = badgeHash; + const params = { courseId: this.courseId, userId: this.userId, badgeHash: badgeHash }; + // @todo use splitview. + // this.splitviewCtrl.push('AddonBadgesIssuedBadgePage', params); + CoreNavHelper.instance.goInSite('/badges/issue', params); + } + +} diff --git a/src/addons/badges/services/badges.ts b/src/addons/badges/services/badges.ts new file mode 100644 index 000000000..2488e5a85 --- /dev/null +++ b/src/addons/badges/services/badges.ts @@ -0,0 +1,213 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { CoreSites } from '@services/sites'; +import { CoreWSExternalWarning } from '@services/ws'; +import { CoreSite } from '@classes/site'; +import { makeSingleton } from '@singletons'; +import { CoreError } from '@classes/errors/error'; + +const ROOT_CACHE_KEY = 'mmaBadges:'; + +/** + * Service to handle badges. + */ +@Injectable({ providedIn: 'root' }) +export class AddonBadgesProvider { + + /** + * Returns whether or not the badge plugin is enabled for a certain site. + * + * This method is called quite often and thus should only perform a quick + * check, we should not be calling WS from here. + * + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with true if enabled, false otherwise. + */ + async isPluginEnabled(siteId?: string): Promise { + const site = await CoreSites.instance.getSite(siteId); + + return site.canUseAdvancedFeature('enablebadges') && site.wsAvailable('core_course_get_user_navigation_options'); + } + + /** + * Get the cache key for the get badges call. + * + * @param courseId ID of the course to get the badges from. + * @param userId ID of the user to get the badges from. + * @return Cache key. + */ + protected getBadgesCacheKey(courseId: number, userId: number): string { + return ROOT_CACHE_KEY + 'badges:' + courseId + ':' + userId; + } + + /** + * Get issued badges for a certain user in a course. + * + * @param courseId ID of the course to get the badges from. + * @param userId ID of the user to get the badges from. + * @param siteId Site ID. If not defined, current site. + * @return Promise to be resolved when the badges are retrieved. + */ + async getUserBadges(courseId: number, userId: number, siteId?: string): Promise { + + const site = await CoreSites.instance.getSite(siteId); + const data: AddonBadgesGetUserBadgesWSParams = { + courseid: courseId, + userid: userId, + }; + const preSets = { + cacheKey: this.getBadgesCacheKey(courseId, userId), + updateFrequency: CoreSite.FREQUENCY_RARELY, + }; + + const response = await site.read('core_badges_get_user_badges', data, preSets); + if (!response || !response.badges) { + throw new CoreError('Invalid badges response'); + } + + // In 3.7, competencies was renamed to alignment. Rename the property in 3.6 too. + response.badges.forEach((badge) => { + badge.alignment = badge.alignment || badge.competencies; + + // Check that the alignment is valid, they were broken in 3.7. + if (badge.alignment && badge.alignment[0] && typeof badge.alignment[0].targetname == 'undefined') { + // If any badge lacks targetname it means they are affected by the Moodle bug, don't display them. + delete badge.alignment; + } + }); + + return response.badges; + } + + /** + * Invalidate get badges WS call. + * + * @param courseId Course ID. + * @param userId ID of the user to get the badges from. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when data is invalidated. + */ + async invalidateUserBadges(courseId: number, userId: number, siteId?: string): Promise { + const site = await CoreSites.instance.getSite(siteId); + + await site.invalidateWsCacheForKey(this.getBadgesCacheKey(courseId, userId)); + } + +} + +export class AddonBadges extends makeSingleton(AddonBadgesProvider) {} + +/** + * Params of core_badges_get_user_badges WS. + */ +type AddonBadgesGetUserBadgesWSParams = { + userid?: number; // Badges only for this user id, empty for current user. + courseid?: number; // Filter badges by course id, empty all the courses. + page?: number; // The page of records to return. + perpage?: number; // The number of records to return per page. + search?: string; // A simple string to search for. + onlypublic?: boolean; // Whether to return only public badges. +}; + +/** + * Data returned by core_badges_get_user_badges WS. + */ +type AddonBadgesGetUserBadgesWSResponse = { + badges: AddonBadgesUserBadge[]; + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Result of WS core_badges_get_user_badges. + */ +export type AddonBadgesGetUserBadgesResult = { + badges: AddonBadgesUserBadge[]; // List of badges. + warnings?: CoreWSExternalWarning[]; // List of warnings. +}; + +/** + * Badge data returned by WS core_badges_get_user_badges. + */ +export type AddonBadgesUserBadge = { + id?: number; // Badge id. + name: string; // Badge name. + description: string; // Badge description. + timecreated?: number; // Time created. + timemodified?: number; // Time modified. + usercreated?: number; // User created. + usermodified?: number; // User modified. + issuername: string; // Issuer name. + issuerurl: string; // Issuer URL. + issuercontact: string; // Issuer contact. + expiredate?: number; // Expire date. + expireperiod?: number; // Expire period. + type?: number; // Type. + courseid?: number; // Course id. + message?: string; // Message. + messagesubject?: string; // Message subject. + attachment?: number; // Attachment. + notification?: number; // @since 3.6. Whether to notify when badge is awarded. + nextcron?: number; // @since 3.6. Next cron. + status?: number; // Status. + issuedid?: number; // Issued id. + uniquehash: string; // Unique hash. + dateissued: number; // Date issued. + dateexpire: number; // Date expire. + visible?: number; // Visible. + email?: string; // @since 3.6. User email. + version?: string; // @since 3.6. Version. + language?: string; // @since 3.6. Language. + imageauthorname?: string; // @since 3.6. Name of the image author. + imageauthoremail?: string; // @since 3.6. Email of the image author. + imageauthorurl?: string; // @since 3.6. URL of the image author. + imagecaption?: string; // @since 3.6. Caption of the image. + badgeurl: string; // Badge URL. + endorsement?: { // @since 3.6. + id: number; // Endorsement id. + badgeid: number; // Badge id. + issuername: string; // Endorsement issuer name. + issuerurl: string; // Endorsement issuer URL. + issueremail: string; // Endorsement issuer email. + claimid: string; // Claim URL. + claimcomment: string; // Claim comment. + dateissued: number; // Date issued. + }; + alignment?: { // @since 3.7. Calculated by the app for 3.6 sites. Badge alignments. + id?: number; // Alignment id. + badgeid?: number; // Badge id. + targetname?: string; // Target name. + targeturl?: string; // Target URL. + targetdescription?: string; // Target description. + targetframework?: string; // Target framework. + targetcode?: string; // Target code. + }[]; + competencies?: { // @deprecated from 3.7. @since 3.6. In 3.7 it was renamed to alignment. + id?: number; // Alignment id. + badgeid?: number; // Badge id. + targetname?: string; // Target name. + targeturl?: string; // Target URL. + targetdescription?: string; // Target description. + targetframework?: string; // Target framework. + targetcode?: string; // Target code. + }[]; + relatedbadges?: { // @since 3.6. Related badges. + id: number; // Badge id. + name: string; // Badge name. + version?: string; // Version. + language?: string; // Language. + type?: number; // Type. + }[]; +}; diff --git a/src/addons/badges/services/handlers/badge-link.ts b/src/addons/badges/services/handlers/badge-link.ts new file mode 100644 index 000000000..13d54b376 --- /dev/null +++ b/src/addons/badges/services/handlers/badge-link.ts @@ -0,0 +1,71 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { Params } from '@angular/router'; +import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; +import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; +import { CoreNavHelper } from '@services/nav-helper'; +import { makeSingleton } from '@singletons'; +import { AddonBadges } from '../badges'; + + +/** + * Handler to treat links to user participants page. + */ +@Injectable({ providedIn: 'root' }) +export class AddonBadgesBadgeLinkHandlerService extends CoreContentLinksHandlerBase { + + name = 'AddonBadgesBadgeLinkHandler'; + pattern = /\/badges\/badge\.php.*([?&]hash=)/; + + /** + * Get the list of actions for a link (url). + * + * @param siteIds List of sites the URL belongs to. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return List of (or promise resolved with list of) actions. + */ + getActions(siteIds: string[], url: string, params: Params): CoreContentLinksAction[] { + + return [{ + action: (siteId: string): void => { + CoreNavHelper.instance.goInSite( + '/badges/issue', + { courseId: 0, badgeHash: params.hash }, + siteId, + ); + }, + }]; + } + + /** + * Check if the handler is enabled for a certain site (site + user) and a URL. + * If not defined, defaults to true. + * + * @param siteId The site ID. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return Whether the handler is enabled for the URL and site. + */ + isEnabled(siteId: string): Promise { + return AddonBadges.instance.isPluginEnabled(siteId); + } + +} + +export class AddonBadgesBadgeLinkHandler extends makeSingleton(AddonBadgesBadgeLinkHandlerService) {} diff --git a/src/addons/badges/services/handlers/mybadges-link.ts b/src/addons/badges/services/handlers/mybadges-link.ts new file mode 100644 index 000000000..9e0c873c9 --- /dev/null +++ b/src/addons/badges/services/handlers/mybadges-link.ts @@ -0,0 +1,58 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; +import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; +import { CoreNavHelper } from '@services/nav-helper'; +import { makeSingleton } from '@singletons'; +import { AddonBadges } from '../badges'; + +/** + * Handler to treat links to user badges page. + */ +@Injectable({ providedIn: 'root' }) +export class AddonBadgesMyBadgesLinkHandlerService extends CoreContentLinksHandlerBase { + + name = 'AddonBadgesMyBadgesLinkHandler'; + featureName = 'CoreUserDelegate_AddonBadges'; + pattern = /\/badges\/mybadges\.php/; + + /** + * Get the list of actions for a link (url). + * + * @return List of (or promise resolved with list of) actions. + */ + getActions(): CoreContentLinksAction[] { + return [{ + action: (siteId: string): void => { + CoreNavHelper.instance.goInSite('/badges/user', {}, siteId); + }, + }]; + } + + /** + * Check if the handler is enabled for a certain site (site + user) and a URL. + * If not defined, defaults to true. + * + * @param siteId The site ID. + * @return Whether the handler is enabled for the URL and site. + */ + isEnabled(siteId: string): boolean | Promise { + return AddonBadges.instance.isPluginEnabled(siteId); + } + +} + +export class AddonBadgesMyBadgesLinkHandler extends makeSingleton(AddonBadgesMyBadgesLinkHandlerService) {} diff --git a/src/addons/badges/services/handlers/user.ts b/src/addons/badges/services/handlers/user.ts new file mode 100644 index 000000000..aad3c5ac2 --- /dev/null +++ b/src/addons/badges/services/handlers/user.ts @@ -0,0 +1,82 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { CoreCourseUserAdminOrNavOptionIndexed } from '@features/courses/services/courses'; +import { CoreUserProfile } from '@features/user/services/user'; +import { CoreUserDelegateService, CoreUserProfileHandler, CoreUserProfileHandlerData } from '@features/user/services/user-delegate'; +import { CoreNavHelper } from '@services/nav-helper'; +import { makeSingleton } from '@singletons'; +import { AddonBadges } from '../badges'; + +/** + * Profile badges handler. + */ +@Injectable({ providedIn: 'root' }) +export class AddonBadgesUserHandlerService implements CoreUserProfileHandler { + + name = 'AddonBadges'; + priority = 50; + type = CoreUserDelegateService.TYPE_NEW_PAGE; + + /** + * Check if handler is enabled. + * + * @return Always enabled. + */ + isEnabled(): Promise { + return AddonBadges.instance.isPluginEnabled(); + } + + /** + * Check if handler is enabled for this user in this context. + * + * @param user User to check. + * @param courseId Course ID. + * @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. + * @return True if enabled, false otherwise. + */ + async isEnabledForUser( + user: CoreUserProfile, + courseId: number, + navOptions?: CoreCourseUserAdminOrNavOptionIndexed, + ): Promise { + if (navOptions && typeof navOptions.badges != 'undefined') { + return navOptions.badges; + } + + // If we reach here, it means we are opening the user site profile. + return true; + } + + /** + * Returns the data needed to render the handler. + * + * @return Data needed to render the handler. + */ + getDisplayData(): CoreUserProfileHandlerData { + return { + icon: 'fas-trophy', + title: 'addon.badges.badges', + action: (event, user, courseId): void => { + event.preventDefault(); + event.stopPropagation(); + CoreNavHelper.instance.goInSite('/badges/user', { courseId: courseId || 0, userId: user.id }); + }, + }; + } + +} + +export class AddonBadgesUserHandler extends makeSingleton(AddonBadgesUserHandlerService) {} diff --git a/src/core/features/user/pages/profile/profile.html b/src/core/features/user/pages/profile/profile.html index d7f1c466b..0b1befa4f 100644 --- a/src/core/features/user/pages/profile/profile.html +++ b/src/core/features/user/pages/profile/profile.html @@ -49,7 +49,7 @@