forked from CIT/Vmeda.Online
		
	
						commit
						a833cd6807
					
				| @ -489,6 +489,12 @@ | |||||||
|   "addon.mod_forum.couldnotadd": "forum", |   "addon.mod_forum.couldnotadd": "forum", | ||||||
|   "addon.mod_forum.cutoffdatereached": "forum", |   "addon.mod_forum.cutoffdatereached": "forum", | ||||||
|   "addon.mod_forum.discussion": "forum", |   "addon.mod_forum.discussion": "forum", | ||||||
|  |   "addon.mod_forum.discussionlistsortbycreatedasc": "forum", | ||||||
|  |   "addon.mod_forum.discussionlistsortbycreateddesc": "forum", | ||||||
|  |   "addon.mod_forum.discussionlistsortbylastpostasc": "forum", | ||||||
|  |   "addon.mod_forum.discussionlistsortbylastpostdesc": "forum", | ||||||
|  |   "addon.mod_forum.discussionlistsortbyrepliesasc": "forum", | ||||||
|  |   "addon.mod_forum.discussionlistsortbyrepliesdesc": "forum", | ||||||
|   "addon.mod_forum.discussionlocked": "forum", |   "addon.mod_forum.discussionlocked": "forum", | ||||||
|   "addon.mod_forum.discussionpinned": "forum", |   "addon.mod_forum.discussionpinned": "forum", | ||||||
|   "addon.mod_forum.discussionsubscription": "forum", |   "addon.mod_forum.discussionsubscription": "forum", | ||||||
| @ -1734,6 +1740,7 @@ | |||||||
|   "core.sizemb": "moodle", |   "core.sizemb": "moodle", | ||||||
|   "core.sizetb": "local_moodlemobileapp", |   "core.sizetb": "local_moodlemobileapp", | ||||||
|   "core.sorry": "local_moodlemobileapp", |   "core.sorry": "local_moodlemobileapp", | ||||||
|  |   "core.sort": "moodle", | ||||||
|   "core.sortby": "moodle", |   "core.sortby": "moodle", | ||||||
|   "core.start": "grouptool", |   "core.start": "grouptool", | ||||||
|   "core.strftimedate": "langconfig", |   "core.strftimedate": "langconfig", | ||||||
|  | |||||||
| @ -8,6 +8,7 @@ | |||||||
|         <core-context-menu-item *ngIf="loaded && (hasOffline || hasOfflineRatings) && isOnline"  [priority]="600" [content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(null, $event, true)" [iconAction]="syncIcon" [closeOnClick]="false"></core-context-menu-item> |         <core-context-menu-item *ngIf="loaded && (hasOffline || hasOfflineRatings) && isOnline"  [priority]="600" [content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(null, $event, true)" [iconAction]="syncIcon" [closeOnClick]="false"></core-context-menu-item> | ||||||
|         <core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch()" [iconAction]="prefetchStatusIcon" [closeOnClick]="false"></core-context-menu-item> |         <core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch()" [iconAction]="prefetchStatusIcon" [closeOnClick]="false"></core-context-menu-item> | ||||||
|         <core-context-menu-item *ngIf="size" [priority]="400" [content]="size" [iconDescription]="'cube'" (action)="removeFiles()" [iconAction]="'trash'"></core-context-menu-item> |         <core-context-menu-item *ngIf="size" [priority]="400" [content]="size" [iconDescription]="'cube'" (action)="removeFiles()" [iconAction]="'trash'"></core-context-menu-item> | ||||||
|  |         <core-context-menu-item *ngIf="sortingAvailable" [priority]="300" [content]="'core.sort' | translate" (action)="showSortOrderSelector($event)" iconAction="fa-sort"></core-context-menu-item> | ||||||
|     </core-context-menu> |     </core-context-menu> | ||||||
| </core-navbar-buttons> | </core-navbar-buttons> | ||||||
| 
 | 
 | ||||||
| @ -30,6 +31,21 @@ | |||||||
|                 <ion-icon name="information-circle"></ion-icon> {{ availabilityMessage }} |                 <ion-icon name="information-circle"></ion-icon> {{ availabilityMessage }} | ||||||
|             </ion-card> |             </ion-card> | ||||||
| 
 | 
 | ||||||
|  |             <core-empty-box *ngIf="forum && discussions.length == 0" icon="chatbubbles" [message]="'addon.mod_forum.forumnodiscussionsyet' | translate" class="core-empty-box-clickable"> | ||||||
|  |                 <div padding *ngIf="forum.cancreatediscussions"> | ||||||
|  |                     <button ion-button block (click)="openNewDiscussion()"> | ||||||
|  |                         {{ 'addon.mod_forum.addanewdiscussion' | translate }} | ||||||
|  |                     </button> | ||||||
|  |                 </div> | ||||||
|  |             </core-empty-box> | ||||||
|  | 
 | ||||||
|  |             <div text-wrap *ngIf="sortingAvailable && selectedSortOrder" ion-row padding-horizontal padding-top margin-bottom> | ||||||
|  |                 <button *ngIf="sortingAvailable" ion-button padding-horizontal icon-end ion-col (click)="showSortOrderSelector($event)" color="light" class="core-button-select button-no-uppercase" [attr.aria-label]="('core.sort' | translate)" aria-haspopup="true" [attr.aria-expanded]="sortOrderSelectorExpanded" aria-controls="addon-mod-forum-sort-order-selector" id="addon-mod-forum-sort-order-button"> | ||||||
|  |                     <span class="core-section-selector-text">{{ selectedSortOrder.label | translate }}</span> | ||||||
|  |                     <ion-icon name="arrow-dropdown" ios="md-arrow-dropdown"></ion-icon> | ||||||
|  |                 </button> | ||||||
|  |             </div> | ||||||
|  | 
 | ||||||
|             <ng-container *ngIf="forum && discussions.length > 0"> |             <ng-container *ngIf="forum && discussions.length > 0"> | ||||||
|                 <ion-card *ngFor="let discussion of offlineDiscussions" (click)="openNewDiscussion(discussion.timecreated)" [class.addon-forum-discussion-selected]="discussion.timecreated == -selectedDiscussion"> |                 <ion-card *ngFor="let discussion of offlineDiscussions" (click)="openNewDiscussion(discussion.timecreated)" [class.addon-forum-discussion-selected]="discussion.timecreated == -selectedDiscussion"> | ||||||
|                     <ion-item text-wrap> |                     <ion-item text-wrap> | ||||||
| @ -86,14 +102,6 @@ | |||||||
|                 </ion-card> |                 </ion-card> | ||||||
|             </ng-container> |             </ng-container> | ||||||
| 
 | 
 | ||||||
|             <core-empty-box *ngIf="forum && discussions.length == 0" icon="chatbubbles" [message]="'addon.mod_forum.forumnodiscussionsyet' | translate" class="core-empty-box-clickable"> |  | ||||||
|                 <div padding *ngIf="forum.cancreatediscussions"> |  | ||||||
|                     <button ion-button block (click)="openNewDiscussion()"> |  | ||||||
|                         {{ 'addon.mod_forum.addanewdiscussion' | translate }} |  | ||||||
|                     </button> |  | ||||||
|                 </div> |  | ||||||
|             </core-empty-box> |  | ||||||
| 
 |  | ||||||
|             <core-infinite-loading [enabled]="canLoadMore" (action)="fetchMoreDiscussions($event)" [error]="loadMoreError"></core-infinite-loading> |             <core-infinite-loading [enabled]="canLoadMore" (action)="fetchMoreDiscussions($event)" [error]="loadMoreError"></core-infinite-loading> | ||||||
|         </core-loading> |         </core-loading> | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -2,7 +2,15 @@ ion-app.app-root addon-mod-forum-index { | |||||||
|     .addon-forum-discussion-selected { |     .addon-forum-discussion-selected { | ||||||
|         border-top: 5px solid $core-splitview-selected; |         border-top: 5px solid $core-splitview-selected; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     .addon-forum-star { |     .addon-forum-star { | ||||||
|         color: $core-star-color; |         color: $core-star-color; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     button.core-button-select .core-section-selector-text { | ||||||
|  |         overflow: hidden; | ||||||
|  |         text-overflow: ellipsis; | ||||||
|  |         line-height: 2em; | ||||||
|  |         white-space: nowrap; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -13,7 +13,7 @@ | |||||||
| // limitations under the License.
 | // limitations under the License.
 | ||||||
| 
 | 
 | ||||||
| import { Component, Optional, Injector, ViewChild } from '@angular/core'; | import { Component, Optional, Injector, ViewChild } from '@angular/core'; | ||||||
| import { Content, NavController } from 'ionic-angular'; | import { Content, ModalController, NavController } from 'ionic-angular'; | ||||||
| import { CoreSplitViewComponent } from '@components/split-view/split-view'; | import { CoreSplitViewComponent } from '@components/split-view/split-view'; | ||||||
| import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main-activity-component'; | import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main-activity-component'; | ||||||
| import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate'; | import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate'; | ||||||
| @ -52,6 +52,11 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom | |||||||
|     addDiscussionText = this.translate.instant('addon.mod_forum.addanewdiscussion'); |     addDiscussionText = this.translate.instant('addon.mod_forum.addanewdiscussion'); | ||||||
|     availabilityMessage: string; |     availabilityMessage: string; | ||||||
| 
 | 
 | ||||||
|  |     sortingAvailable: boolean; | ||||||
|  |     sortOrders = []; | ||||||
|  |     selectedSortOrder = null; | ||||||
|  |     sortOrderSelectorExpanded = false; | ||||||
|  | 
 | ||||||
|     protected syncEventName = AddonModForumSyncProvider.AUTO_SYNCED; |     protected syncEventName = AddonModForumSyncProvider.AUTO_SYNCED; | ||||||
|     protected page = 0; |     protected page = 0; | ||||||
|     protected trackPosts = false; |     protected trackPosts = false; | ||||||
| @ -69,6 +74,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom | |||||||
|     constructor(injector: Injector, |     constructor(injector: Injector, | ||||||
|             @Optional() protected content: Content, |             @Optional() protected content: Content, | ||||||
|             protected navCtrl: NavController, |             protected navCtrl: NavController, | ||||||
|  |             protected modalCtrl: ModalController, | ||||||
|             protected groupsProvider: CoreGroupsProvider, |             protected groupsProvider: CoreGroupsProvider, | ||||||
|             protected userProvider: CoreUserProvider, |             protected userProvider: CoreUserProvider, | ||||||
|             protected forumProvider: AddonModForumProvider, |             protected forumProvider: AddonModForumProvider, | ||||||
| @ -79,6 +85,9 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom | |||||||
|             protected prefetchHandler: AddonModForumPrefetchHandler, |             protected prefetchHandler: AddonModForumPrefetchHandler, | ||||||
|             protected ratingOffline: CoreRatingOfflineProvider) { |             protected ratingOffline: CoreRatingOfflineProvider) { | ||||||
|         super(injector); |         super(injector); | ||||||
|  | 
 | ||||||
|  |         this.sortingAvailable = this.forumProvider.isDiscussionListSortingAvailable(); | ||||||
|  |         this.sortOrders = this.forumProvider.getAvailableSortOrders(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -162,7 +171,9 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom | |||||||
|     protected fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<any> { |     protected fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<any> { | ||||||
|         this.loadMoreError = false; |         this.loadMoreError = false; | ||||||
| 
 | 
 | ||||||
|         return this.forumProvider.getForum(this.courseId, this.module.id).then((forum) => { |         const promises = []; | ||||||
|  | 
 | ||||||
|  |         promises.push(this.forumProvider.getForum(this.courseId, this.module.id).then((forum) => { | ||||||
|             this.forum = forum; |             this.forum = forum; | ||||||
| 
 | 
 | ||||||
|             this.description = forum.intro || this.description; |             this.description = forum.intro || this.description; | ||||||
| @ -212,7 +223,11 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom | |||||||
|                     this.canAddDiscussion = this.forum.cancreatediscussions && !cutoffDateReached; |                     this.canAddDiscussion = this.forum.cancreatediscussions && !cutoffDateReached; | ||||||
|                 }), |                 }), | ||||||
|             ]); |             ]); | ||||||
|         }).then(() => { |         })); | ||||||
|  | 
 | ||||||
|  |         promises.push(this.fetchSortOrderPreference()); | ||||||
|  | 
 | ||||||
|  |         return Promise.all(promises).then(() => { | ||||||
|             return Promise.all([ |             return Promise.all([ | ||||||
|                 this.fetchOfflineDiscussion(), |                 this.fetchOfflineDiscussion(), | ||||||
|                 this.fetchDiscussions(refresh), |                 this.fetchDiscussions(refresh), | ||||||
| @ -291,7 +306,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom | |||||||
|             this.page = 0; |             this.page = 0; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return this.forumProvider.getDiscussions(this.forum.id, this.page).then((response) => { |         return this.forumProvider.getDiscussions(this.forum.id, this.selectedSortOrder.value, this.page).then((response) => { | ||||||
|             let promise; |             let promise; | ||||||
|             if (this.usesGroups) { |             if (this.usesGroups) { | ||||||
|                 promise = this.forumProvider.formatDiscussionsGroups(this.forum.cmid, response.discussions); |                 promise = this.forumProvider.formatDiscussionsGroups(this.forum.cmid, response.discussions); | ||||||
| @ -366,6 +381,27 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Convenience function to fetch the sort order preference. | ||||||
|  |      * | ||||||
|  |      * @return {Promise<any>} Promise resolved when done. | ||||||
|  |      */ | ||||||
|  |     protected fetchSortOrderPreference(): Promise<any> { | ||||||
|  |         let promise; | ||||||
|  |         if (this.sortingAvailable) { | ||||||
|  |             promise = this.userProvider.getUserPreference(AddonModForumProvider.PREFERENCE_SORTORDER).then((value) => { | ||||||
|  |                 return value ? parseInt(value, 10) : null; | ||||||
|  |             }); | ||||||
|  |         } else { | ||||||
|  |             // Use default.
 | ||||||
|  |             promise = Promise.resolve(null); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return promise.then((value) => { | ||||||
|  |            this.selectedSortOrder = this.sortOrders.find((sortOrder) => sortOrder.value === value) || this.sortOrders[0]; | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Perform the invalidate content function. |      * Perform the invalidate content function. | ||||||
|      * |      * | ||||||
| @ -382,6 +418,10 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom | |||||||
|             promises.push(this.forumProvider.invalidateAccessInformation(this.forum.id)); |             promises.push(this.forumProvider.invalidateAccessInformation(this.forum.id)); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         if (this.sortingAvailable) { | ||||||
|  |             promises.push(this.userProvider.invalidateUserPreference(AddonModForumProvider.PREFERENCE_SORTORDER)); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         return Promise.all(promises); |         return Promise.all(promises); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -484,6 +524,37 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom | |||||||
|         this.selectedDiscussion = 0; |         this.selectedDiscussion = 0; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Display the sort order selector modal. | ||||||
|  |      * | ||||||
|  |      * @param {MouseEvent} event Event. | ||||||
|  |      */ | ||||||
|  |     showSortOrderSelector(event: MouseEvent): void { | ||||||
|  |         if (!this.sortingAvailable) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const params = { sortOrders: this.sortOrders, selected: this.selectedSortOrder.value }; | ||||||
|  |         const modal = this.modalCtrl.create('AddonModForumSortOrderSelectorPage', params); | ||||||
|  |         modal.onDidDismiss((sortOrder) => { | ||||||
|  |             this.sortOrderSelectorExpanded = false; | ||||||
|  | 
 | ||||||
|  |             if (sortOrder && sortOrder.value != this.selectedSortOrder.value) { | ||||||
|  |                 this.selectedSortOrder = sortOrder; | ||||||
|  |                 this.page = 0; | ||||||
|  |                 this.userProvider.setUserPreference(AddonModForumProvider.PREFERENCE_SORTORDER, sortOrder.value.toFixed(0)) | ||||||
|  |                         .then(() => { | ||||||
|  |                     this.showLoadingAndFetch(); | ||||||
|  |                 }).catch((error) => { | ||||||
|  |                     this.domUtils.showErrorModalDefault(error, 'Error updating preference.'); | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         modal.present({ev: event}); | ||||||
|  |         this.sortOrderSelectorExpanded = true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Component being destroyed. |      * Component being destroyed. | ||||||
|      */ |      */ | ||||||
|  | |||||||
| @ -10,6 +10,12 @@ | |||||||
|     "couldnotadd": "Could not add your post due to an unknown error", |     "couldnotadd": "Could not add your post due to an unknown error", | ||||||
|     "cutoffdatereached": "The cut-off date for posting to this forum is reached so you can no longer post to it.", |     "cutoffdatereached": "The cut-off date for posting to this forum is reached so you can no longer post to it.", | ||||||
|     "discussion": "Discussion", |     "discussion": "Discussion", | ||||||
|  |     "discussionlistsortbycreatedasc": "Sort by creation date in ascending order", | ||||||
|  |     "discussionlistsortbycreateddesc": "Sort by creation date in descending order", | ||||||
|  |     "discussionlistsortbylastpostasc": "Sort by last post creation date in ascending order", | ||||||
|  |     "discussionlistsortbylastpostdesc": "Sort by last post creation date in descending order", | ||||||
|  |     "discussionlistsortbyrepliesasc": "Sort by number of replies in ascending order", | ||||||
|  |     "discussionlistsortbyrepliesdesc": "Sort by number of replies in descending order", | ||||||
|     "discussionlocked": "This discussion has been locked so you can no longer reply to it.", |     "discussionlocked": "This discussion has been locked so you can no longer reply to it.", | ||||||
|     "discussionpinned": "Pinned", |     "discussionpinned": "Pinned", | ||||||
|     "discussionsubscription": "Discussion subscription", |     "discussionsubscription": "Discussion subscription", | ||||||
|  | |||||||
| @ -0,0 +1,19 @@ | |||||||
|  | <ion-header> | ||||||
|  |     <ion-navbar core-back-button> | ||||||
|  |         <ion-title>{{ 'core.sort' | translate }}</ion-title> | ||||||
|  |         <ion-buttons end> | ||||||
|  |             <button ion-button icon-only (click)="closeModal()" [attr.aria-label]="'core.close' | translate"> | ||||||
|  |                 <ion-icon name="close"></ion-icon> | ||||||
|  |             </button> | ||||||
|  |         </ion-buttons> | ||||||
|  |     </ion-navbar> | ||||||
|  | </ion-header> | ||||||
|  | <ion-content> | ||||||
|  |     <ion-list id="addon-mod-forum-sort-selector" role="menu" aria-labelledby="addon-mod-forum-sort-order-button"> | ||||||
|  |         <ng-container *ngFor="let sortOrder of sortOrders"> | ||||||
|  |             <a ion-item text-wrap (click)="selectSortOrder(sortOrder)" [class.core-primary-selected-item]="selected == sortOrder.value" detail-none role="menuitem" [attr.aria-label]="sortOrder.label | translate"> | ||||||
|  |                 <h2><core-format-text [text]="sortOrder.label | translate"></core-format-text></h2> | ||||||
|  |             </a> | ||||||
|  |         </ng-container> | ||||||
|  |     </ion-list> | ||||||
|  | </ion-content> | ||||||
| @ -0,0 +1,33 @@ | |||||||
|  | // (C) Copyright 2015 Martin Dougiamas
 | ||||||
|  | //
 | ||||||
|  | // 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 { IonicPageModule } from 'ionic-angular'; | ||||||
|  | import { TranslateModule } from '@ngx-translate/core'; | ||||||
|  | import { CoreComponentsModule } from '@components/components.module'; | ||||||
|  | import { CoreDirectivesModule } from '@directives/directives.module'; | ||||||
|  | import { AddonModForumSortOrderSelectorPage } from './sort-order-selector'; | ||||||
|  | 
 | ||||||
|  | @NgModule({ | ||||||
|  |     declarations: [ | ||||||
|  |         AddonModForumSortOrderSelectorPage, | ||||||
|  |     ], | ||||||
|  |     imports: [ | ||||||
|  |         CoreComponentsModule, | ||||||
|  |         CoreDirectivesModule, | ||||||
|  |         IonicPageModule.forChild(AddonModForumSortOrderSelectorPage), | ||||||
|  |         TranslateModule.forChild() | ||||||
|  |     ], | ||||||
|  | }) | ||||||
|  | export class AddonModForumSortOrderSelectorPagePageModule {} | ||||||
| @ -0,0 +1,51 @@ | |||||||
|  | // (C) Copyright 2015 Martin Dougiamas
 | ||||||
|  | //
 | ||||||
|  | // 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 } from '@angular/core'; | ||||||
|  | import { IonicPage, NavParams, ViewController } from 'ionic-angular'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Page that displays the sort selector. | ||||||
|  |  */ | ||||||
|  | @IonicPage({ segment: 'addon-mod-forum-sort-order-selector' }) | ||||||
|  | @Component({ | ||||||
|  |     selector: 'page-addon-mod-forum-sort-order-selector', | ||||||
|  |     templateUrl: 'sort-order-selector.html', | ||||||
|  | }) | ||||||
|  | export class AddonModForumSortOrderSelectorPage { | ||||||
|  | 
 | ||||||
|  |     sortOrders = []; | ||||||
|  |     selected: number; | ||||||
|  | 
 | ||||||
|  |     constructor(navParams: NavParams, private viewCtrl: ViewController) { | ||||||
|  |         this.sortOrders = navParams.get('sortOrders'); | ||||||
|  |         this.selected = navParams.get('selected'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Close the modal. | ||||||
|  |      */ | ||||||
|  |     closeModal(): void { | ||||||
|  |         this.viewCtrl.dismiss(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Select a sort order. | ||||||
|  |      * | ||||||
|  |      * @param {any} sortOrder Selected sort order. | ||||||
|  |      */ | ||||||
|  |     selectSortOrder(sortOrder: any): void { | ||||||
|  |         this.viewCtrl.dismiss(sortOrder); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -14,7 +14,7 @@ | |||||||
| 
 | 
 | ||||||
| import { Injectable } from '@angular/core'; | import { Injectable } from '@angular/core'; | ||||||
| import { TranslateService } from '@ngx-translate/core'; | import { TranslateService } from '@ngx-translate/core'; | ||||||
| import { CoreSite } from '@classes/site'; | import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; | ||||||
| import { CoreAppProvider } from '@providers/app'; | import { CoreAppProvider } from '@providers/app'; | ||||||
| import { CoreFilepoolProvider } from '@providers/filepool'; | import { CoreFilepoolProvider } from '@providers/filepool'; | ||||||
| import { CoreGroupsProvider } from '@providers/groups'; | import { CoreGroupsProvider } from '@providers/groups'; | ||||||
| @ -38,6 +38,14 @@ export class AddonModForumProvider { | |||||||
|     static CHANGE_DISCUSSION_EVENT = 'addon_mod_forum_lock_discussion'; |     static CHANGE_DISCUSSION_EVENT = 'addon_mod_forum_lock_discussion'; | ||||||
|     static MARK_READ_EVENT = 'addon_mod_forum_mark_read'; |     static MARK_READ_EVENT = 'addon_mod_forum_mark_read'; | ||||||
| 
 | 
 | ||||||
|  |     static PREFERENCE_SORTORDER = 'forum_discussionlistsortorder'; | ||||||
|  |     static SORTORDER_LASTPOST_DESC = 1; | ||||||
|  |     static SORTORDER_LASTPOST_ASC = 2; | ||||||
|  |     static SORTORDER_CREATED_DESC = 3; | ||||||
|  |     static SORTORDER_CREATED_ASC = 4; | ||||||
|  |     static SORTORDER_REPLIES_DESC = 5; | ||||||
|  |     static SORTORDER_REPLIES_ASC = 6; | ||||||
|  | 
 | ||||||
|     protected ROOT_CACHE_KEY = 'mmaModForum:'; |     protected ROOT_CACHE_KEY = 'mmaModForum:'; | ||||||
| 
 | 
 | ||||||
|     constructor(private appProvider: CoreAppProvider, |     constructor(private appProvider: CoreAppProvider, | ||||||
| @ -58,7 +66,7 @@ export class AddonModForumProvider { | |||||||
|      * @return {string}         Cache key. |      * @return {string}         Cache key. | ||||||
|      */ |      */ | ||||||
|     protected getCanAddDiscussionCacheKey(forumId: number, groupId: number): string { |     protected getCanAddDiscussionCacheKey(forumId: number, groupId: number): string { | ||||||
|         return this.getCommonCanAddDiscussionCacheKey(forumId) + ':' + groupId; |         return this.getCommonCanAddDiscussionCacheKey(forumId) + groupId; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -68,7 +76,7 @@ export class AddonModForumProvider { | |||||||
|      * @return {string}         Cache key. |      * @return {string}         Cache key. | ||||||
|      */ |      */ | ||||||
|     protected getCommonCanAddDiscussionCacheKey(forumId: number): string { |     protected getCommonCanAddDiscussionCacheKey(forumId: number): string { | ||||||
|         return this.ROOT_CACHE_KEY + 'canadddiscussion:' + forumId; |         return this.ROOT_CACHE_KEY + 'canadddiscussion:' + forumId + ':'; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -105,10 +113,17 @@ export class AddonModForumProvider { | |||||||
|      * Get cache key for forum discussions list WS calls. |      * Get cache key for forum discussions list WS calls. | ||||||
|      * |      * | ||||||
|      * @param  {number} forumId Forum ID. |      * @param  {number} forumId Forum ID. | ||||||
|  |      * @param  {number} sortOrder Sort order. | ||||||
|      * @return {string} Cache key. |      * @return {string} Cache key. | ||||||
|      */ |      */ | ||||||
|     protected getDiscussionsListCacheKey(forumId: number): string { |     protected getDiscussionsListCacheKey(forumId: number, sortOrder: number): string { | ||||||
|         return this.ROOT_CACHE_KEY + 'discussions:' + forumId; |         let key = this.ROOT_CACHE_KEY + 'discussions:' + forumId; | ||||||
|  | 
 | ||||||
|  |         if (sortOrder != AddonModForumProvider.SORTORDER_LASTPOST_DESC) { | ||||||
|  |             key += ':' + sortOrder; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return key; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -452,10 +467,64 @@ export class AddonModForumProvider { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Return whether discussion lists can be sorted. | ||||||
|  |      * | ||||||
|  |      * @param {CoreSite} [site] Site. If not defined, current site. | ||||||
|  |      * @return {boolean} True if discussion lists can be sorted. | ||||||
|  |      */ | ||||||
|  |     isDiscussionListSortingAvailable(site?: CoreSite): boolean { | ||||||
|  |         site = site || this.sitesProvider.getCurrentSite(); | ||||||
|  | 
 | ||||||
|  |         return site.isVersionGreaterEqualThan('3.7'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Return the list of available sort orders. | ||||||
|  |      * | ||||||
|  |      * @return {{label: string, value: number}[]} List of sort orders. | ||||||
|  |      */ | ||||||
|  |     getAvailableSortOrders(): {label: string, value: number}[] { | ||||||
|  |         const sortOrders = [ | ||||||
|  |             { | ||||||
|  |                 label: 'addon.mod_forum.discussionlistsortbylastpostdesc', | ||||||
|  |                 value: AddonModForumProvider.SORTORDER_LASTPOST_DESC | ||||||
|  |             }, | ||||||
|  |         ]; | ||||||
|  | 
 | ||||||
|  |         if (this.isDiscussionListSortingAvailable()) { | ||||||
|  |             sortOrders.push( | ||||||
|  |                 { | ||||||
|  |                     label: 'addon.mod_forum.discussionlistsortbylastpostasc', | ||||||
|  |                     value: AddonModForumProvider.SORTORDER_LASTPOST_ASC | ||||||
|  |                 }, | ||||||
|  |                 { | ||||||
|  |                     label: 'addon.mod_forum.discussionlistsortbycreateddesc', | ||||||
|  |                     value: AddonModForumProvider.SORTORDER_CREATED_DESC | ||||||
|  |                 }, | ||||||
|  |                 { | ||||||
|  |                     label: 'addon.mod_forum.discussionlistsortbycreatedasc', | ||||||
|  |                     value: AddonModForumProvider.SORTORDER_CREATED_ASC | ||||||
|  |                 }, | ||||||
|  |                 { | ||||||
|  |                     label: 'addon.mod_forum.discussionlistsortbyrepliesdesc', | ||||||
|  |                     value: AddonModForumProvider.SORTORDER_REPLIES_DESC | ||||||
|  |                 }, | ||||||
|  |                 { | ||||||
|  |                     label: 'addon.mod_forum.discussionlistsortbyrepliesasc', | ||||||
|  |                     value: AddonModForumProvider.SORTORDER_REPLIES_ASC | ||||||
|  |                 } | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return sortOrders; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Get forum discussions. |      * Get forum discussions. | ||||||
|      * |      * | ||||||
|      * @param  {number}  forumId      Forum ID. |      * @param  {number}  forumId      Forum ID. | ||||||
|  |      * @param  {number}  [sortOrder]  Sort order. | ||||||
|      * @param  {number}  [page=0]     Page. |      * @param  {number}  [page=0]     Page. | ||||||
|      * @param  {boolean} [forceCache] True to always get the value from cache. false otherwise. |      * @param  {boolean} [forceCache] True to always get the value from cache. false otherwise. | ||||||
|      * @param  {string}  [siteId]     Site ID. If not defined, current site. |      * @param  {string}  [siteId]     Site ID. If not defined, current site. | ||||||
| @ -463,23 +532,59 @@ export class AddonModForumProvider { | |||||||
|      *                                 - discussions: List of discussions. |      *                                 - discussions: List of discussions. | ||||||
|      *                                 - canLoadMore: True if there may be more discussions to load. |      *                                 - canLoadMore: True if there may be more discussions to load. | ||||||
|      */ |      */ | ||||||
|     getDiscussions(forumId: number, page: number = 0, forceCache?: boolean, siteId?: string): Promise<any> { |     getDiscussions(forumId: number, sortOrder?: number, page: number = 0, forceCache?: boolean, siteId?: string): Promise<any> { | ||||||
|  |         sortOrder = sortOrder || AddonModForumProvider.SORTORDER_LASTPOST_DESC; | ||||||
|  | 
 | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             const params = { |             let method = 'mod_forum_get_forum_discussions_paginated'; | ||||||
|  |             const params: any = { | ||||||
|                 forumid: forumId, |                 forumid: forumId, | ||||||
|                 sortby:  'timemodified', |  | ||||||
|                 sortdirection:  'DESC', |  | ||||||
|                 page: page, |                 page: page, | ||||||
|                 perpage: AddonModForumProvider.DISCUSSIONS_PER_PAGE |                 perpage: AddonModForumProvider.DISCUSSIONS_PER_PAGE | ||||||
|             }; |             }; | ||||||
|             const preSets: any = { | 
 | ||||||
|                 cacheKey: this.getDiscussionsListCacheKey(forumId) |             if (site.wsAvailable('mod_forum_get_forum_discussions')) { | ||||||
|  |                 // Since Moodle 3.7.
 | ||||||
|  |                 method = 'mod_forum_get_forum_discussions'; | ||||||
|  |                 params.sortorder = sortOrder; | ||||||
|  |             } else { | ||||||
|  |                 if (sortOrder == AddonModForumProvider.SORTORDER_LASTPOST_DESC) { | ||||||
|  |                     params.sortby = 'timemodified'; | ||||||
|  |                     params.sortdirection = 'DESC'; | ||||||
|  |                 } else { | ||||||
|  |                     // Sorting not supported with the old WS method.
 | ||||||
|  |                     return Promise.reject(null); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             const preSets: CoreSiteWSPreSets = { | ||||||
|  |                 cacheKey: this.getDiscussionsListCacheKey(forumId, sortOrder) | ||||||
|             }; |             }; | ||||||
|             if (forceCache) { |             if (forceCache) { | ||||||
|                 preSets.omitExpires = true; |                 preSets.omitExpires = true; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             return site.read('mod_forum_get_forum_discussions_paginated', params, preSets).then((response) => { |             return site.read(method, params, preSets).catch((error) => { | ||||||
|  |                 // Try to get the data from cache stored with the old WS method.
 | ||||||
|  |                 if (!this.appProvider.isOnline() && method == 'mod_forum_get_forum_discussion' && | ||||||
|  |                         sortOrder == AddonModForumProvider.SORTORDER_LASTPOST_DESC) { | ||||||
|  | 
 | ||||||
|  |                     const params = { | ||||||
|  |                         forumid: forumId, | ||||||
|  |                         page: page, | ||||||
|  |                         perpage: AddonModForumProvider.DISCUSSIONS_PER_PAGE, | ||||||
|  |                         sortby: 'timemodified', | ||||||
|  |                         sortdirection: 'DESC' | ||||||
|  |                     }; | ||||||
|  |                     const preSets: CoreSiteWSPreSets = { | ||||||
|  |                         cacheKey: this.getDiscussionsListCacheKey(forumId, sortOrder), | ||||||
|  |                         omitExpires: true | ||||||
|  |                     }; | ||||||
|  | 
 | ||||||
|  |                     return site.read('mod_forum_get_forum_discussions_paginated', params, preSets); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 return Promise.reject(error); | ||||||
|  |             }).then((response) => { | ||||||
|                 if (response) { |                 if (response) { | ||||||
|                     this.storeUserData(response.discussions); |                     this.storeUserData(response.discussions); | ||||||
| 
 | 
 | ||||||
| @ -499,7 +604,8 @@ export class AddonModForumProvider { | |||||||
|      * If a page fails, the discussions until that page will be returned along with a flag indicating an error occurred. |      * If a page fails, the discussions until that page will be returned along with a flag indicating an error occurred. | ||||||
|      * |      * | ||||||
|      * @param  {number}  forumId     Forum ID. |      * @param  {number}  forumId     Forum ID. | ||||||
|      * @param  {boolean} forceCache  True to always get the value from cache, false otherwise. |      * @param  {number}  [sortOrder] Sort order. | ||||||
|  |      * @param  {boolean} [forceCache] True to always get the value from cache, false otherwise. | ||||||
|      * @param  {number}  [numPages]  Number of pages to get. If not defined, all pages. |      * @param  {number}  [numPages]  Number of pages to get. If not defined, all pages. | ||||||
|      * @param  {number}  [startPage] Page to start. If not defined, first page. |      * @param  {number}  [startPage] Page to start. If not defined, first page. | ||||||
|      * @param  {string}  [siteId]    Site ID. If not defined, current site. |      * @param  {string}  [siteId]    Site ID. If not defined, current site. | ||||||
| @ -507,8 +613,8 @@ export class AddonModForumProvider { | |||||||
|      *                                - discussions: List of discussions. |      *                                - discussions: List of discussions. | ||||||
|      *                                - error: True if an error occurred, false otherwise. |      *                                - error: True if an error occurred, false otherwise. | ||||||
|      */ |      */ | ||||||
|     getDiscussionsInPages(forumId: number, forceCache?: boolean, numPages?: number, startPage?: number, siteId?: string) |     getDiscussionsInPages(forumId: number, sortOrder?: number, forceCache?: boolean, numPages?: number, startPage?: number, | ||||||
|             : Promise<any> { |             siteId?: string): Promise<any> { | ||||||
|         if (typeof numPages == 'undefined') { |         if (typeof numPages == 'undefined') { | ||||||
|             numPages = -1; |             numPages = -1; | ||||||
|         } |         } | ||||||
| @ -525,7 +631,7 @@ export class AddonModForumProvider { | |||||||
| 
 | 
 | ||||||
|         const getPage = (page: number): Promise<any> => { |         const getPage = (page: number): Promise<any> => { | ||||||
|             // Get page discussions.
 |             // Get page discussions.
 | ||||||
|             return this.getDiscussions(forumId, page, forceCache, siteId).then((response) => { |             return this.getDiscussions(forumId, sortOrder, page, forceCache, siteId).then((response) => { | ||||||
|                 result.discussions = result.discussions.concat(response.discussions); |                 result.discussions = result.discussions.concat(response.discussions); | ||||||
|                 numPages--; |                 numPages--; | ||||||
| 
 | 
 | ||||||
| @ -569,9 +675,6 @@ export class AddonModForumProvider { | |||||||
|     invalidateContent(moduleId: number, courseId: number): Promise<any> { |     invalidateContent(moduleId: number, courseId: number): Promise<any> { | ||||||
|         // Get the forum first, we need the forum ID.
 |         // Get the forum first, we need the forum ID.
 | ||||||
|         return this.getForum(courseId, moduleId).then((forum) => { |         return this.getForum(courseId, moduleId).then((forum) => { | ||||||
|             // We need to get the list of discussions to be able to invalidate their posts.
 |  | ||||||
|             return this.getDiscussionsInPages(forum.id, true).then((response) => { |  | ||||||
|                 // Now invalidate the WS calls.
 |  | ||||||
|             const promises = []; |             const promises = []; | ||||||
| 
 | 
 | ||||||
|             promises.push(this.invalidateForumData(courseId)); |             promises.push(this.invalidateForumData(courseId)); | ||||||
| @ -579,12 +682,25 @@ export class AddonModForumProvider { | |||||||
|             promises.push(this.invalidateCanAddDiscussion(forum.id)); |             promises.push(this.invalidateCanAddDiscussion(forum.id)); | ||||||
|             promises.push(this.invalidateAccessInformation(forum.id)); |             promises.push(this.invalidateAccessInformation(forum.id)); | ||||||
| 
 | 
 | ||||||
|  |             this.getAvailableSortOrders().forEach((sortOrder) => { | ||||||
|  |                 // We need to get the list of discussions to be able to invalidate their posts.
 | ||||||
|  |                 promises.push(this.getDiscussionsInPages(forum.id, sortOrder.value, true).then((response) => { | ||||||
|  |                     // Now invalidate the WS calls.
 | ||||||
|  |                     const promises = []; | ||||||
|  | 
 | ||||||
|                     response.discussions.forEach((discussion) => { |                     response.discussions.forEach((discussion) => { | ||||||
|                         promises.push(this.invalidateDiscussionPosts(discussion.discussion)); |                         promises.push(this.invalidateDiscussionPosts(discussion.discussion)); | ||||||
|                     }); |                     }); | ||||||
| 
 | 
 | ||||||
|                     return this.utils.allPromises(promises); |                     return this.utils.allPromises(promises); | ||||||
|  |                 })); | ||||||
|             }); |             }); | ||||||
|  | 
 | ||||||
|  |             if (this.isDiscussionListSortingAvailable()) { | ||||||
|  |                 promises.push(this.userProvider.invalidateUserPreference(AddonModForumProvider.PREFERENCE_SORTORDER)); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return this.utils.allPromises(promises); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -623,7 +739,9 @@ export class AddonModForumProvider { | |||||||
|      */ |      */ | ||||||
|     invalidateDiscussionsList(forumId: number, siteId?: string): Promise<any> { |     invalidateDiscussionsList(forumId: number, siteId?: string): Promise<any> { | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             return site.invalidateWsCacheForKey(this.getDiscussionsListCacheKey(forumId)); |             return this.utils.allPromises(this.getAvailableSortOrders().map((sortOrder) => { | ||||||
|  |                 return site.invalidateWsCacheForKey(this.getDiscussionsListCacheKey(forumId, sortOrder.value)); | ||||||
|  |             })); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -162,7 +162,7 @@ export class AddonModForumHelperProvider { | |||||||
|         siteId = siteId || this.sitesProvider.getCurrentSiteId(); |         siteId = siteId || this.sitesProvider.getCurrentSiteId(); | ||||||
| 
 | 
 | ||||||
|         const findDiscussion = (page: number): Promise<any> => { |         const findDiscussion = (page: number): Promise<any> => { | ||||||
|             return this.forumProvider.getDiscussions(forumId, page, false, siteId).then((response) => { |             return this.forumProvider.getDiscussions(forumId, undefined, page, false, siteId).then((response) => { | ||||||
|                 if (response.discussions && response.discussions.length > 0) { |                 if (response.discussions && response.discussions.length > 0) { | ||||||
|                     const discussion = response.discussions.find((discussion) => discussion.id == discussionId); |                     const discussion = response.discussions.find((discussion) => discussion.id == discussionId); | ||||||
|                     if (discussion) { |                     if (discussion) { | ||||||
|  | |||||||
| @ -25,7 +25,7 @@ import { CoreGroupsProvider } from '@providers/groups'; | |||||||
| import { CoreUserProvider } from '@core/user/providers/user'; | import { CoreUserProvider } from '@core/user/providers/user'; | ||||||
| import { AddonModForumProvider } from './forum'; | import { AddonModForumProvider } from './forum'; | ||||||
| import { AddonModForumSyncProvider } from './sync'; | import { AddonModForumSyncProvider } from './sync'; | ||||||
| import { CoreRatingProvider } from '@core/rating/providers/rating'; | import { CoreRatingProvider, CoreRatingInfo } from '@core/rating/providers/rating'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Handler to prefetch forums. |  * Handler to prefetch forums. | ||||||
| @ -107,26 +107,36 @@ export class AddonModForumPrefetchHandler extends CoreCourseActivityPrefetchHand | |||||||
|      * @return {Promise<any[]>} Promise resolved with array of posts. |      * @return {Promise<any[]>} Promise resolved with array of posts. | ||||||
|      */ |      */ | ||||||
|     protected getPostsForPrefetch(forum: any): Promise<any[]> { |     protected getPostsForPrefetch(forum: any): Promise<any[]> { | ||||||
|  |         const posts = {}; | ||||||
|  |         const ratingInfos: CoreRatingInfo[] = []; | ||||||
|  | 
 | ||||||
|  |         const promises = this.forumProvider.getAvailableSortOrders().map((sortOrder) => { | ||||||
|             // Get discussions in first 2 pages.
 |             // Get discussions in first 2 pages.
 | ||||||
|         return this.forumProvider.getDiscussionsInPages(forum.id, false, 2).then((response) => { |             return this.forumProvider.getDiscussionsInPages(forum.id, sortOrder.value, false, 2).then((response) => { | ||||||
|                 if (response.error) { |                 if (response.error) { | ||||||
|                     return Promise.reject(null); |                     return Promise.reject(null); | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 const promises = []; |                 const promises = []; | ||||||
|             let posts = []; |  | ||||||
| 
 | 
 | ||||||
|                 response.discussions.forEach((discussion) => { |                 response.discussions.forEach((discussion) => { | ||||||
|                     promises.push(this.forumProvider.getDiscussionPosts(discussion.discussion).then((response) => { |                     promises.push(this.forumProvider.getDiscussionPosts(discussion.discussion).then((response) => { | ||||||
|                     posts = posts.concat(response.posts); |                         response.posts.forEach((post) => { | ||||||
| 
 |                             posts[post.id] = post; | ||||||
|                     return this.ratingProvider.prefetchRatings('module', forum.cmid, forum.scale, forum.course, |                         }); | ||||||
|                             response.ratinginfo); |                         ratingInfos.push(response.ratinginfo); | ||||||
|                     })); |                     })); | ||||||
|                 }); |                 }); | ||||||
| 
 | 
 | ||||||
|  |               return Promise.all(promises); | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|         return Promise.all(promises).then(() => { |         return Promise.all(promises).then(() => { | ||||||
|                 return posts; |             const ratingInfo = this.ratingProvider.mergeRatingInfos(ratingInfos); | ||||||
|  | 
 | ||||||
|  |             return this.ratingProvider.prefetchRatings('module', forum.cmid, forum.scale, forum.course, ratingInfo).then(() => { | ||||||
|  |                 return this.utils.objectToArray(posts); | ||||||
|             }); |             }); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| @ -210,6 +220,11 @@ export class AddonModForumPrefetchHandler extends CoreCourseActivityPrefetchHand | |||||||
|             // Prefetch access information.
 |             // Prefetch access information.
 | ||||||
|             promises.push(this.forumProvider.getAccessInformation(forum.id)); |             promises.push(this.forumProvider.getAccessInformation(forum.id)); | ||||||
| 
 | 
 | ||||||
|  |             // Prefetch sort order preference.
 | ||||||
|  |             if (this.forumProvider.isDiscussionListSortingAvailable()) { | ||||||
|  |                promises.push(this.userProvider.getUserPreference(AddonModForumProvider.PREFERENCE_SORTORDER)); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             return Promise.all(promises); |             return Promise.all(promises); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -489,6 +489,12 @@ | |||||||
|     "addon.mod_forum.couldnotadd": "Could not add your post due to an unknown error", |     "addon.mod_forum.couldnotadd": "Could not add your post due to an unknown error", | ||||||
|     "addon.mod_forum.cutoffdatereached": "The cut-off date for posting to this forum is reached so you can no longer post to it.", |     "addon.mod_forum.cutoffdatereached": "The cut-off date for posting to this forum is reached so you can no longer post to it.", | ||||||
|     "addon.mod_forum.discussion": "Discussion", |     "addon.mod_forum.discussion": "Discussion", | ||||||
|  |     "addon.mod_forum.discussionlistsortbycreatedasc": "Sort by creation date in ascending order", | ||||||
|  |     "addon.mod_forum.discussionlistsortbycreateddesc": "Sort by creation date in descending order", | ||||||
|  |     "addon.mod_forum.discussionlistsortbylastpostasc": "Sort by last post creation date in ascending order", | ||||||
|  |     "addon.mod_forum.discussionlistsortbylastpostdesc": "Sort by last post creation date in descending order", | ||||||
|  |     "addon.mod_forum.discussionlistsortbyrepliesasc": "Sort by number of replies in ascending order", | ||||||
|  |     "addon.mod_forum.discussionlistsortbyrepliesdesc": "Sort by number of replies in descending order", | ||||||
|     "addon.mod_forum.discussionlocked": "This discussion has been locked so you can no longer reply to it.", |     "addon.mod_forum.discussionlocked": "This discussion has been locked so you can no longer reply to it.", | ||||||
|     "addon.mod_forum.discussionpinned": "Pinned", |     "addon.mod_forum.discussionpinned": "Pinned", | ||||||
|     "addon.mod_forum.discussionsubscription": "Discussion subscription", |     "addon.mod_forum.discussionsubscription": "Discussion subscription", | ||||||
| @ -1734,6 +1740,7 @@ | |||||||
|     "core.sizemb": "MB", |     "core.sizemb": "MB", | ||||||
|     "core.sizetb": "TB", |     "core.sizetb": "TB", | ||||||
|     "core.sorry": "Sorry...", |     "core.sorry": "Sorry...", | ||||||
|  |     "core.sort": "Sort", | ||||||
|     "core.sortby": "Sort by", |     "core.sortby": "Sort by", | ||||||
|     "core.start": "Start", |     "core.start": "Start", | ||||||
|     "core.strftimedate": "%d %B %Y", |     "core.strftimedate": "%d %B %Y", | ||||||
|  | |||||||
| @ -326,6 +326,44 @@ export class CoreRatingProvider { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Convenience function to merge two or more rating infos of the same instance. | ||||||
|  |      * | ||||||
|  |      * @param {CoreRatingInfo[]} ratingInfos Array of rating infos. | ||||||
|  |      * @return {CoreRatingInfo} Merged rating info or null. | ||||||
|  |      */ | ||||||
|  |     mergeRatingInfos(ratingInfos: CoreRatingInfo[]): CoreRatingInfo { | ||||||
|  |         let result: CoreRatingInfo = null; | ||||||
|  |         const scales = {}; | ||||||
|  |         const ratings = {}; | ||||||
|  | 
 | ||||||
|  |         ratingInfos.forEach((ratingInfo) => { | ||||||
|  |             if (!ratingInfo) { | ||||||
|  |                 // Skip null rating infos.
 | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (!result) { | ||||||
|  |                 result = Object.assign({}, ratingInfo); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             (ratingInfo.scales || []).forEach((scale) => { | ||||||
|  |                 scales[scale.id] = scale; | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             (ratingInfo.ratings || []).forEach((rating) => { | ||||||
|  |                 ratings[rating.itemid] = rating; | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         if (result) { | ||||||
|  |             result.scales = this.utils.objectToArray(scales); | ||||||
|  |             result.ratings = this.utils.objectToArray(ratings); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return result; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Prefetch individual ratings. |      * Prefetch individual ratings. | ||||||
|      * |      * | ||||||
|  | |||||||
							
								
								
									
										114
									
								
								src/core/user/providers/offline.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								src/core/user/providers/offline.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,114 @@ | |||||||
|  | // (C) Copyright 2015 Martin Dougiamas
 | ||||||
|  | //
 | ||||||
|  | // 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 { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Structure of offline user preferences. | ||||||
|  |  */ | ||||||
|  | export interface CoreUserOfflinePreference { | ||||||
|  |     name: string; | ||||||
|  |     value: string; | ||||||
|  |     onlinevalue: string; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Service to handle offline user preferences. | ||||||
|  |  */ | ||||||
|  | @Injectable() | ||||||
|  | export class CoreUserOfflineProvider { | ||||||
|  | 
 | ||||||
|  |     // Variables for database.
 | ||||||
|  |     static PREFERENCES_TABLE = 'user_preferences'; | ||||||
|  |     protected siteSchema: CoreSiteSchema = { | ||||||
|  |         name: 'CoreUserOfflineProvider', | ||||||
|  |         version: 1, | ||||||
|  |         tables: [ | ||||||
|  |             { | ||||||
|  |                 name: CoreUserOfflineProvider.PREFERENCES_TABLE, | ||||||
|  |                 columns: [ | ||||||
|  |                     { | ||||||
|  |                         name: 'name', | ||||||
|  |                         type: 'TEXT', | ||||||
|  |                         unique: true, | ||||||
|  |                         notNull: true | ||||||
|  |                     }, | ||||||
|  |                     { | ||||||
|  |                         name: 'value', | ||||||
|  |                         type: 'TEXT' | ||||||
|  |                     }, | ||||||
|  |                     { | ||||||
|  |                         name: 'onlinevalue', | ||||||
|  |                         type: 'TEXT' | ||||||
|  |                     }, | ||||||
|  |                 ] | ||||||
|  |             } | ||||||
|  |         ] | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     constructor(private sitesProvider: CoreSitesProvider) { | ||||||
|  |         this.sitesProvider.registerSiteSchema(this.siteSchema); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get preferences that were changed offline. | ||||||
|  |      * | ||||||
|  |      * @return {Promise<CoreUserOfflinePreference[]>} Promise resolved with list of preferences. | ||||||
|  |      */ | ||||||
|  |     getChangedPreferences(siteId?: string): Promise<CoreUserOfflinePreference[]> { | ||||||
|  |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|  |             return site.getDb().getRecordsSelect(CoreUserOfflineProvider.PREFERENCES_TABLE, 'value != onlineValue'); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get an offline preference. | ||||||
|  |      * | ||||||
|  |      * @param {string} name Name of the preference. | ||||||
|  |      * @return {Promise<CoreUserOfflinePreference>} Promise resolved with the preference, rejected if not found. | ||||||
|  |      */ | ||||||
|  |     getPreference(name: string, siteId?: string): Promise<CoreUserOfflinePreference> { | ||||||
|  |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|  |             const conditions = { name }; | ||||||
|  | 
 | ||||||
|  |             return site.getDb().getRecord(CoreUserOfflineProvider.PREFERENCES_TABLE, conditions); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Set an offline preference. | ||||||
|  |      * | ||||||
|  |      * @param {string} name Name of the preference. | ||||||
|  |      * @param {string} value Value of the preference. | ||||||
|  |      * @param {string} onlineValue Online value of the preference. If unedfined, preserve previously stored value. | ||||||
|  |      * @return {Promise<CoreUserPreference>} Promise resolved when done. | ||||||
|  |      */ | ||||||
|  |     setPreference(name: string, value: string, onlineValue?: string, siteId?: string): Promise<any> { | ||||||
|  |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|  |             let promise: Promise<string>; | ||||||
|  |             if (typeof onlineValue == 'undefined') { | ||||||
|  |                 promise = this.getPreference(name, site.id).then((preference) => preference.onlinevalue); | ||||||
|  |             } else { | ||||||
|  |                 promise = Promise.resolve(onlineValue); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return promise.then((onlineValue) => { | ||||||
|  |                 const record = { name, value, onlinevalue: onlineValue }; | ||||||
|  | 
 | ||||||
|  |                 return site.getDb().insertRecord(CoreUserOfflineProvider.PREFERENCES_TABLE, record); | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										48
									
								
								src/core/user/providers/sync-cron-handler.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/core/user/providers/sync-cron-handler.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,48 @@ | |||||||
|  | // (C) Copyright 2015 Martin Dougiamas
 | ||||||
|  | //
 | ||||||
|  | // 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 { CoreCronHandler } from '@providers/cron'; | ||||||
|  | import { CoreUserSyncProvider } from './sync'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Synchronization cron handler. | ||||||
|  |  */ | ||||||
|  | @Injectable() | ||||||
|  | export class CoreUserSyncCronHandler implements CoreCronHandler { | ||||||
|  |     name = 'CoreUserSyncCronHandler'; | ||||||
|  | 
 | ||||||
|  |     constructor(private userSync: CoreUserSyncProvider) {} | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Execute the process. | ||||||
|  |      * Receives the ID of the site affected, undefined for all sites. | ||||||
|  |      * | ||||||
|  |      * @param {string} [siteId] ID of the site affected, undefined for all sites. | ||||||
|  |      * @param {boolean} [force] Wether the execution is forced (manual sync). | ||||||
|  |      * @return {Promise<any>} Promise resolved when done, rejected if failure. | ||||||
|  |      */ | ||||||
|  |     execute(siteId?: string, force?: boolean): Promise<any> { | ||||||
|  |         return this.userSync.syncPreferences(siteId); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get the time between consecutive executions. | ||||||
|  |      * | ||||||
|  |      * @return {number} Time between consecutive executions (in ms). | ||||||
|  |      */ | ||||||
|  |     getInterval(): number { | ||||||
|  |         return 300000; // 5 minutes.
 | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										100
									
								
								src/core/user/providers/sync.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								src/core/user/providers/sync.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,100 @@ | |||||||
|  | // (C) Copyright 2015 Martin Dougiamas
 | ||||||
|  | //
 | ||||||
|  | // 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 { CoreLoggerProvider } from '@providers/logger'; | ||||||
|  | import { CoreSitesProvider } from '@providers/sites'; | ||||||
|  | import { CoreSyncBaseProvider } from '@classes/base-sync'; | ||||||
|  | import { CoreAppProvider } from '@providers/app'; | ||||||
|  | import { CoreUserOfflineProvider } from './offline'; | ||||||
|  | import { CoreUserProvider } from './user'; | ||||||
|  | import { CoreTextUtilsProvider } from '@providers/utils/text'; | ||||||
|  | import { CoreTimeUtilsProvider } from '@providers/utils/time'; | ||||||
|  | import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||||
|  | import { TranslateService } from '@ngx-translate/core'; | ||||||
|  | import { CoreSyncProvider } from '@providers/sync'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Service to sync user preferences. | ||||||
|  |  */ | ||||||
|  | @Injectable() | ||||||
|  | export class CoreUserSyncProvider extends CoreSyncBaseProvider { | ||||||
|  | 
 | ||||||
|  |     static AUTO_SYNCED = 'core_user_autom_synced'; | ||||||
|  | 
 | ||||||
|  |     constructor(loggerProvider: CoreLoggerProvider, sitesProvider: CoreSitesProvider, appProvider: CoreAppProvider, | ||||||
|  |             translate: TranslateService, syncProvider: CoreSyncProvider, textUtils: CoreTextUtilsProvider, | ||||||
|  |             private userOffline: CoreUserOfflineProvider, private userProvider: CoreUserProvider, | ||||||
|  |             private utils: CoreUtilsProvider, timeUtils: CoreTimeUtilsProvider) { | ||||||
|  |         super('CoreUserSync', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Try to synchronize user preferences in a certain site or in all sites. | ||||||
|  |      * | ||||||
|  |      * @param {string} [siteId] Site ID to sync. If not defined, sync all sites. | ||||||
|  |      * @return {Promise<any>} Promise resolved if sync is successful, rejected if sync fails. | ||||||
|  |      */ | ||||||
|  |     syncPreferences(siteId?: string): Promise<any> { | ||||||
|  |         const syncFunctionLog = 'all user preferences'; | ||||||
|  | 
 | ||||||
|  |         return this.syncOnSites(syncFunctionLog, this.syncPreferencesFunc.bind(this), [], siteId); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Sync user preferences of a site. | ||||||
|  |      * | ||||||
|  |      * @param {string} [siteId] Site ID to sync. If not defined, sync all sites. | ||||||
|  |      * @param {Promise<any>} Promise resolved if sync is successful, rejected if sync fails. | ||||||
|  |      */ | ||||||
|  |     protected syncPreferencesFunc(siteId?: string): Promise<any> { | ||||||
|  |         siteId = siteId || this.sitesProvider.getCurrentSiteId(); | ||||||
|  | 
 | ||||||
|  |         const syncId = 'preferences'; | ||||||
|  | 
 | ||||||
|  |         if (this.isSyncing(syncId, siteId)) { | ||||||
|  |             // There's already a sync ongoing, return the promise.
 | ||||||
|  |             return this.getOngoingSync(syncId, siteId); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const warnings = []; | ||||||
|  | 
 | ||||||
|  |         this.logger.debug(`Try to sync user preferences`); | ||||||
|  | 
 | ||||||
|  |         const syncPromise = this.userOffline.getChangedPreferences(siteId).then((preferences) => { | ||||||
|  |             return this.utils.allPromises(preferences.map((preference) => { | ||||||
|  |                 return this.userProvider.getUserPreferenceOnline(preference.name, siteId).then((onlineValue) => { | ||||||
|  |                     if (preference.onlinevalue != onlineValue) { | ||||||
|  |                         // Prefernce was changed on web while the app was offline, do not sync.
 | ||||||
|  |                         return this.userOffline.setPreference(preference.name, onlineValue, onlineValue, siteId); | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     return this.userProvider.setUserPreference(name, preference.value, siteId).catch((error) => { | ||||||
|  |                         if (this.utils.isWebServiceError(error)) { | ||||||
|  |                             warnings.push(this.textUtils.getErrorMessageFromError(error)); | ||||||
|  |                         } else { | ||||||
|  |                             // Couldn't connect to server, reject.
 | ||||||
|  |                             return Promise.reject(error); | ||||||
|  |                         } | ||||||
|  |                     }); | ||||||
|  |                 }); | ||||||
|  |             })); | ||||||
|  |         }).then(() => { | ||||||
|  |             // All done, return the warnings.
 | ||||||
|  |             return warnings; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         return this.addOngoingSync(syncId, syncPromise, siteId); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -15,9 +15,11 @@ | |||||||
| import { Injectable } from '@angular/core'; | import { Injectable } from '@angular/core'; | ||||||
| import { CoreFilepoolProvider } from '@providers/filepool'; | import { CoreFilepoolProvider } from '@providers/filepool'; | ||||||
| import { CoreLoggerProvider } from '@providers/logger'; | import { CoreLoggerProvider } from '@providers/logger'; | ||||||
| import { CoreSite } from '@classes/site'; | import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; | ||||||
| import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; | import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; | ||||||
| import { CoreUtilsProvider } from '@providers/utils/utils'; | import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||||
|  | import { CoreAppProvider } from '@providers/app'; | ||||||
|  | import { CoreUserOfflineProvider } from './offline'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Service to provide user functionalities. |  * Service to provide user functionalities. | ||||||
| @ -59,7 +61,8 @@ export class CoreUserProvider { | |||||||
|     protected logger; |     protected logger; | ||||||
| 
 | 
 | ||||||
|     constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider, |     constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider, | ||||||
|             private filepoolProvider: CoreFilepoolProvider) { |             private filepoolProvider: CoreFilepoolProvider, private appProvider: CoreAppProvider, | ||||||
|  |             private userOffline: CoreUserOfflineProvider) { | ||||||
|         this.logger = logger.getInstance('CoreUserProvider'); |         this.logger = logger.getInstance('CoreUserProvider'); | ||||||
|         this.sitesProvider.registerSiteSchema(this.siteSchema); |         this.sitesProvider.registerSiteSchema(this.siteSchema); | ||||||
|     } |     } | ||||||
| @ -272,6 +275,67 @@ export class CoreUserProvider { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Get a user preference (online or offline). | ||||||
|  |      * | ||||||
|  |      * @param {string} name Name of the preference. | ||||||
|  |      * @param {string} [siteId] Site Id. If not defined, use current site. | ||||||
|  |      * @return {string} Preference value or null if preference not set. | ||||||
|  |      */ | ||||||
|  |     getUserPreference(name: string, siteId?: string): Promise<string> { | ||||||
|  |         siteId = siteId || this.sitesProvider.getCurrentSiteId(); | ||||||
|  | 
 | ||||||
|  |         return this.userOffline.getPreference(name, siteId).catch(() => { | ||||||
|  |             return null; | ||||||
|  |         }).then((preference) => { | ||||||
|  |             if (preference && !this.appProvider.isOnline()) { | ||||||
|  |                 // Offline, return stored value.
 | ||||||
|  |                 return preference.value; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return this.getUserPreferenceOnline(name, siteId).then((wsValue) => { | ||||||
|  |                 if (preference && preference.value != preference.onlinevalue && preference.onlinevalue == wsValue) { | ||||||
|  |                     // Sync is pending for this preference, return stored value.
 | ||||||
|  |                     return preference.value; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 return this.userOffline.setPreference(name, wsValue, wsValue).then(() => { | ||||||
|  |                     return wsValue; | ||||||
|  |                 }); | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get cache key for a user preference WS call. | ||||||
|  |      * | ||||||
|  |      * @param {string} name Preference name. | ||||||
|  |      * @return {string} Cache key. | ||||||
|  |      */ | ||||||
|  |     protected getUserPreferenceCacheKey(name: string): string { | ||||||
|  |         return this.ROOT_CACHE_KEY + 'preference:' + name; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get a user preference online. | ||||||
|  |      * | ||||||
|  |      * @param {string} name Name of the preference. | ||||||
|  |      * @param {string} [siteId] Site Id. If not defined, use current site. | ||||||
|  |      * @return {string} Preference value or null if preference not set. | ||||||
|  |      */ | ||||||
|  |     getUserPreferenceOnline(name: string, siteId?: string): Promise<string> { | ||||||
|  |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|  |             const data = { name }; | ||||||
|  |             const preSets: CoreSiteWSPreSets = { | ||||||
|  |                 cacheKey: this.getUserPreferenceCacheKey(data.name) | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             return site.read('core_user_get_user_preferences', data, preSets).then((result) => { | ||||||
|  |                 return result.preferences[0] ? result.preferences[0].value : null; | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Invalidates user WS calls. |      * Invalidates user WS calls. | ||||||
|      * |      * | ||||||
| @ -298,6 +362,19 @@ export class CoreUserProvider { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Invalidate user preference. | ||||||
|  |      * | ||||||
|  |      * @param {string} name Name of the preference. | ||||||
|  |      * @param {string} [siteId] Site Id. If not defined, use current site. | ||||||
|  |      * @return {Promise<any>} Promise resolved when the data is invalidated. | ||||||
|  |      */ | ||||||
|  |     invalidateUserPreference(name: string, siteId?: string): Promise<any> { | ||||||
|  |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|  |             return site.invalidateWsCacheForKey(this.getUserPreferenceCacheKey(name)); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Check if course participants is disabled in a certain site. |      * Check if course participants is disabled in a certain site. | ||||||
|      * |      * | ||||||
| @ -455,6 +532,45 @@ export class CoreUserProvider { | |||||||
|         return Promise.all(promises); |         return Promise.all(promises); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Set a user preference (online or offline). | ||||||
|  |      * | ||||||
|  |      * @param {string} name Name of the preference. | ||||||
|  |      * @param {string} value Value of the preference. | ||||||
|  |      * @param {string} [siteId] Site ID. If not defined, current site. | ||||||
|  |      * @return {Promise<any>} Promise resolved on success. | ||||||
|  |      */ | ||||||
|  |     setUserPreference(name: string, value: string, siteId?: string): Promise<any> { | ||||||
|  |         siteId = siteId || this.sitesProvider.getCurrentSiteId(); | ||||||
|  | 
 | ||||||
|  |         let isOnline = this.appProvider.isOnline(); | ||||||
|  |         let promise: Promise<any>; | ||||||
|  | 
 | ||||||
|  |         if (isOnline) { | ||||||
|  |             const preferences = [{type: name, value}]; | ||||||
|  |             promise = this.updateUserPreferences(preferences, undefined, undefined, siteId).catch((error) => { | ||||||
|  |                 // Preference not saved online.
 | ||||||
|  |                 isOnline = false; | ||||||
|  | 
 | ||||||
|  |                 return Promise.reject(error); | ||||||
|  |             }); | ||||||
|  |         } else { | ||||||
|  |             promise = Promise.resolve(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return promise.finally(() => { | ||||||
|  |             // Update stored online value if saved online.
 | ||||||
|  |             const onlineValue = isOnline ? value : undefined; | ||||||
|  | 
 | ||||||
|  |             return Promise.all([ | ||||||
|  |                 this.userOffline.setPreference(name, value, onlineValue), | ||||||
|  |                 this.invalidateUserPreference(name).catch(() => { | ||||||
|  |                     // Ignore error.
 | ||||||
|  |                 }) | ||||||
|  |             ]); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Update a preference for a user. |      * Update a preference for a user. | ||||||
|      * |      * | ||||||
| @ -478,13 +594,14 @@ export class CoreUserProvider { | |||||||
|     /** |     /** | ||||||
|      * Update some preferences for a user. |      * Update some preferences for a user. | ||||||
|      * |      * | ||||||
|      * @param  {any} preferences                List of preferences. |      * @param  {{name: string, value: string}[]} preferences List of preferences. | ||||||
|      * @param  {boolean} [disableNotifications] Whether to disable all notifications. Undefined to not update this value. |      * @param  {boolean} [disableNotifications] Whether to disable all notifications. Undefined to not update this value. | ||||||
|      * @param  {number} [userId]                User ID. If not defined, site's current user. |      * @param  {number} [userId]                User ID. If not defined, site's current user. | ||||||
|      * @param  {string} [siteId]                Site ID. If not defined, current site. |      * @param  {string} [siteId]                Site ID. If not defined, current site. | ||||||
|      * @return {Promise<any>}                   Promise resolved if success. |      * @return {Promise<any>}                   Promise resolved if success. | ||||||
|      */ |      */ | ||||||
|     updateUserPreferences(preferences: any, disableNotifications: boolean, userId?: number, siteId?: string): Promise<any> { |     updateUserPreferences(preferences: {type: string, value: string}[], disableNotifications?: boolean, userId?: number, | ||||||
|  |             siteId?: string): Promise<any> { | ||||||
|         return this.sitesProvider.getSite(siteId).then((site) => { |         return this.sitesProvider.getSite(siteId).then((site) => { | ||||||
|             userId = userId || site.getUserId(); |             userId = userId || site.getUserId(); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -26,6 +26,10 @@ import { CoreUserParticipantsCourseOptionHandler } from './providers/course-opti | |||||||
| import { CoreUserParticipantsLinkHandler } from './providers/participants-link-handler'; | import { CoreUserParticipantsLinkHandler } from './providers/participants-link-handler'; | ||||||
| import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delegate'; | import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delegate'; | ||||||
| import { CoreUserComponentsModule } from './components/components.module'; | import { CoreUserComponentsModule } from './components/components.module'; | ||||||
|  | import { CoreCronDelegate } from '@providers/cron'; | ||||||
|  | import { CoreUserOfflineProvider } from './providers/offline'; | ||||||
|  | import { CoreUserSyncProvider } from './providers/sync'; | ||||||
|  | import { CoreUserSyncCronHandler } from './providers/sync-cron-handler'; | ||||||
| 
 | 
 | ||||||
| // List of providers (without handlers).
 | // List of providers (without handlers).
 | ||||||
| export const CORE_USER_PROVIDERS: any[] = [ | export const CORE_USER_PROVIDERS: any[] = [ | ||||||
| @ -33,6 +37,8 @@ export const CORE_USER_PROVIDERS: any[] = [ | |||||||
|     CoreUserProfileFieldDelegate, |     CoreUserProfileFieldDelegate, | ||||||
|     CoreUserProvider, |     CoreUserProvider, | ||||||
|     CoreUserHelperProvider, |     CoreUserHelperProvider, | ||||||
|  |     CoreUserOfflineProvider, | ||||||
|  |     CoreUserSyncProvider | ||||||
| ]; | ]; | ||||||
| 
 | 
 | ||||||
| @NgModule({ | @NgModule({ | ||||||
| @ -46,10 +52,13 @@ export const CORE_USER_PROVIDERS: any[] = [ | |||||||
|         CoreUserProfileFieldDelegate, |         CoreUserProfileFieldDelegate, | ||||||
|         CoreUserProvider, |         CoreUserProvider, | ||||||
|         CoreUserHelperProvider, |         CoreUserHelperProvider, | ||||||
|  |         CoreUserOfflineProvider, | ||||||
|  |         CoreUserSyncProvider, | ||||||
|         CoreUserProfileMailHandler, |         CoreUserProfileMailHandler, | ||||||
|         CoreUserProfileLinkHandler, |         CoreUserProfileLinkHandler, | ||||||
|         CoreUserParticipantsCourseOptionHandler, |         CoreUserParticipantsCourseOptionHandler, | ||||||
|         CoreUserParticipantsLinkHandler |         CoreUserParticipantsLinkHandler, | ||||||
|  |         CoreUserSyncCronHandler, | ||||||
|     ] |     ] | ||||||
| }) | }) | ||||||
| export class CoreUserModule { | export class CoreUserModule { | ||||||
| @ -57,12 +66,14 @@ export class CoreUserModule { | |||||||
|             eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, userProvider: CoreUserProvider, |             eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, userProvider: CoreUserProvider, | ||||||
|             contentLinksDelegate: CoreContentLinksDelegate, userLinkHandler: CoreUserProfileLinkHandler, |             contentLinksDelegate: CoreContentLinksDelegate, userLinkHandler: CoreUserProfileLinkHandler, | ||||||
|             courseOptionHandler: CoreUserParticipantsCourseOptionHandler, linkHandler: CoreUserParticipantsLinkHandler, |             courseOptionHandler: CoreUserParticipantsCourseOptionHandler, linkHandler: CoreUserParticipantsLinkHandler, | ||||||
|             courseOptionsDelegate: CoreCourseOptionsDelegate) { |             courseOptionsDelegate: CoreCourseOptionsDelegate, cronDelegate: CoreCronDelegate, | ||||||
|  |             syncHandler: CoreUserSyncCronHandler) { | ||||||
| 
 | 
 | ||||||
|         userDelegate.registerHandler(userProfileMailHandler); |         userDelegate.registerHandler(userProfileMailHandler); | ||||||
|         courseOptionsDelegate.registerHandler(courseOptionHandler); |         courseOptionsDelegate.registerHandler(courseOptionHandler); | ||||||
|         contentLinksDelegate.registerHandler(userLinkHandler); |         contentLinksDelegate.registerHandler(userLinkHandler); | ||||||
|         contentLinksDelegate.registerHandler(linkHandler); |         contentLinksDelegate.registerHandler(linkHandler); | ||||||
|  |         cronDelegate.register(syncHandler); | ||||||
| 
 | 
 | ||||||
|         eventsProvider.on(CoreEventsProvider.USER_DELETED, (data) => { |         eventsProvider.on(CoreEventsProvider.USER_DELETED, (data) => { | ||||||
|             // Search for userid in params.
 |             // Search for userid in params.
 | ||||||
|  | |||||||
| @ -223,6 +223,7 @@ | |||||||
|     "sizemb": "MB", |     "sizemb": "MB", | ||||||
|     "sizetb": "TB", |     "sizetb": "TB", | ||||||
|     "sorry": "Sorry...", |     "sorry": "Sorry...", | ||||||
|  |     "sort": "Sort", | ||||||
|     "sortby": "Sort by", |     "sortby": "Sort by", | ||||||
|     "start": "Start", |     "start": "Start", | ||||||
|     "strftimedate": "%d %B %Y", |     "strftimedate": "%d %B %Y", | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user