MOBILE-2319 notes: Migrate notes

main
Albert Gasset 2018-03-20 11:47:59 +01:00
parent 2fffb47c2c
commit d29712258a
19 changed files with 1618 additions and 5 deletions

View File

@ -0,0 +1,41 @@
// (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 { CommonModule } from '@angular/common';
import { IonicModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreComponentsModule } from '@components/components.module';
import { AddonNotesTypesComponent } from './types/types';
@NgModule({
declarations: [
AddonNotesTypesComponent
],
imports: [
CommonModule,
IonicModule,
TranslateModule.forChild(),
CoreComponentsModule,
],
providers: [
],
exports: [
AddonNotesTypesComponent
],
entryComponents: [
AddonNotesTypesComponent
]
})
export class AddonNotesComponentsModule {}

View File

@ -0,0 +1,15 @@
<core-split-view>
<ion-content>
<ion-list>
<a ion-item (click)="openList('site')" [title]="'addon.notes.sitenotes' | translate" [class.core-split-item-selected]="type == 'site'">
{{ 'addon.notes.sitenotes' | translate }}
</a>
<a ion-item (click)="openList('course')" [title]="'addon.notes.coursenotes' | translate" [class.core-split-item-selected]="type == 'course'">
{{ 'addon.notes.coursenotes' | translate }}
</a>
<a ion-item (click)="openList('personal')" [title]="'addon.notes.personalnotes' | translate" [class.core-split-item-selected]="type == 'personal'">
{{ 'addon.notes.personalnotes' | translate }}
</a>
</ion-list>
</ion-content>
</core-split-view>

View File

@ -0,0 +1,62 @@
// (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 { AfterViewInit, Component, Input, OnInit, ViewChild } from '@angular/core';
import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { CoreSitesProvider } from '@providers/sites';
/**
* Component that displays the competencies of a course.
*/
@Component({
selector: 'addon-notes-types',
templateUrl: 'types.html',
})
export class AddonNotesTypesComponent implements AfterViewInit, OnInit {
@ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent;
@Input() courseId: number;
protected type: string;
constructor(private sitesProvider: CoreSitesProvider) {
}
/**
* Properties initialized.
*/
ngOnInit(): void {
const siteHomeId = this.sitesProvider.getCurrentSite().getSiteHomeId();
this.type = (this.courseId == siteHomeId ? 'site' : 'course');
}
/**
* View loaded.
*/
ngAfterViewInit(): void {
if (this.splitviewCtrl.isOn()) {
this.openList(this.type);
}
}
/**
* Opens a list of notes.
*
* @param {string} type
*/
openList(type: string): void {
this.type = type;
this.splitviewCtrl.push('AddonNotesListPage', {courseId: this.courseId, type: type});
}
}

View File

@ -0,0 +1,13 @@
{
"addnewnote": "Add a new note",
"coursenotes": "Course notes",
"eventnotecreated": "Note created",
"nonotes": "There are no notes of this type yet.",
"note": "Note",
"notes": "Notes",
"personalnotes": "Personal notes",
"publishstate": "Context",
"sitenotes": "Site notes",
"userwithid": "User with ID {{id}}",
"warningnotenotsent": "Couldn't add note(s) to course {{course}}. {{error}}"
}

View File

@ -0,0 +1,67 @@
// (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 { AddonNotesProvider } from './providers/notes';
import { AddonNotesOfflineProvider } from './providers/notes-offline';
import { AddonNotesSyncProvider } from './providers/notes-sync';
import { AddonNotesCourseOptionHandler } from './providers/course-option-handler';
import { AddonNotesSyncCronHandler } from './providers/sync-cron-handler';
import { AddonNotesUserHandler } from './providers/user-handler';
import { AddonNotesComponentsModule } from './components/components.module';
import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delegate';
import { CoreCronDelegate } from '@providers/cron';
import { CoreCoursesProvider } from '@core/courses/providers/courses';
import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider } from '@providers/sites';
import { CoreUserDelegate } from '@core/user/providers/user-delegate';
import { CoreUserProvider } from '@core/user/providers/user';
@NgModule({
declarations: [
],
imports: [
AddonNotesComponentsModule
],
providers: [
AddonNotesProvider,
AddonNotesOfflineProvider,
AddonNotesSyncProvider,
AddonNotesCourseOptionHandler,
AddonNotesSyncCronHandler,
AddonNotesUserHandler
]
})
export class AddonNotesModule {
constructor(courseOptionsDelegate: CoreCourseOptionsDelegate, courseOptionHandler: AddonNotesCourseOptionHandler,
userDelegate: CoreUserDelegate, userHandler: AddonNotesUserHandler, cronDelegate: CoreCronDelegate,
syncHandler: AddonNotesSyncCronHandler, eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider) {
// Register handlers.
courseOptionsDelegate.registerHandler(courseOptionHandler);
userDelegate.registerHandler(userHandler);
cronDelegate.register(syncHandler);
eventsProvider.on(CoreEventsProvider.LOGOUT, () => {
courseOptionHandler.clearCoursesNavCache();
}, sitesProvider.getCurrentSiteId());
eventsProvider.on(CoreCoursesProvider.EVENT_MY_COURSES_REFRESHED, () => {
courseOptionHandler.clearCoursesNavCache();
}, sitesProvider.getCurrentSiteId());
eventsProvider.on(CoreUserProvider.PROFILE_REFRESHED, () => {
userHandler.clearAddNoteCache();
}, sitesProvider.getCurrentSiteId());
}
}

View File

@ -0,0 +1,28 @@
<ion-header>
<ion-navbar>
<ion-title>{{ 'addon.notes.addnewnote' | 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 padding>
<form name="itemEdit" (ngSubmit)="addNote()">
<ion-item>
<ion-label>{{ 'addon.notes.publishstate' | translate }}</ion-label>
<ion-select [(ngModel)]="publishState" name="publishState">
<ion-option value="personal">{{ 'addon.notes.personalnotes' | translate }}</ion-option>
<ion-option value="course">{{ 'addon.notes.coursenotes' | translate }}</ion-option>
<ion-option value="site">{{ 'addon.notes.sitenotes' | translate }}</ion-option>
</ion-select>
</ion-item>
<ion-item>
<ion-textarea placeholder="{{ 'addon.notes.note' | translate }}" rows="5" [(ngModel)]="text" name="text" required="required"></ion-textarea>
</ion-item>
<button ion-button full margin-vertical type="submit" [disabled]="processing || text.length < 2">
{{ 'addon.notes.addnewnote' | translate }}
</button>
</form>
</ion-content>

View File

@ -0,0 +1,31 @@
// (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 { AddonNotesAddPage } from './add';
import { TranslateModule } from '@ngx-translate/core';
import { CoreDirectivesModule } from '@directives/directives.module';
@NgModule({
declarations: [
AddonNotesAddPage
],
imports: [
CoreDirectivesModule,
IonicPageModule.forChild(AddonNotesAddPage),
TranslateModule.forChild()
]
})
export class AddonNotesAddPageModule {}

View File

@ -0,0 +1,69 @@
// (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, ViewController, NavParams } from 'ionic-angular';
import { CoreAppProvider } from '@providers/app';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { AddonNotesProvider } from '../../providers/notes';
/**
* Component that displays a text area for composing a note.
*/
@IonicPage({ segment: 'addon-notes-add' })
@Component({
selector: 'page-addon-notes-add',
templateUrl: 'add.html',
})
export class AddonNotesAddPage {
userId: number;
courseId: number;
publishState = 'personal';
text = '';
processing = false;
constructor(params: NavParams, private viewCtrl: ViewController, private appProvider: CoreAppProvider,
private domUtils: CoreDomUtilsProvider, private notesProvider: AddonNotesProvider) {
this.userId = params.get('userId');
this.courseId = params.get('courseId');
}
/**
* Send the note or store it offline.
*/
addNote(): void {
this.appProvider.closeKeyboard();
const loadingModal = this.domUtils.showModalLoading('core.sending', true);
// Freeze the add note button.
this.processing = true;
this.notesProvider.addNote(this.userId, this.courseId, this.publishState, this.text).then((sent) => {
this.viewCtrl.dismiss().finally(() => {
const message = sent ? 'addon.notes.eventnotecreated' : 'core.datastoredoffline';
this.domUtils.showAlertTranslated('core.success', message);
});
}).catch((error) => {
this.domUtils.showErrorModal(error);
this.processing = false;
}).finally(() => {
loadingModal.dismiss();
});
}
/**
* Close modal.
*/
closeModal(): void {
this.viewCtrl.dismiss();
}
}

View File

@ -0,0 +1,42 @@
<ion-header>
<ion-navbar>
<ion-title>{{ 'addon.notes.notes' | translate }}</ion-title>
<ion-buttons end></ion-buttons>
</ion-navbar>
<core-navbar-buttons end>
<core-context-menu>
<core-context-menu-item [hidden]="!(notesLoaded && !hasOffline)" [priority]="100" [content]="'core.refresh' | translate" (action)="refreshNotes(false)" [iconAction]="refreshIcon" [closeOnClick]="true"></core-context-menu-item>
<core-context-menu-item [hidden]="!(notesLoaded && hasOffline)" [priority]="100" [content]="'core.settings.synchronizenow' | translate" (action)="refreshNotes(true)" [iconAction]="syncIcon" [closeOnClick]="false"></core-context-menu-item>
</core-context-menu>
</core-navbar-buttons>
</ion-header>
<ion-content>
<ion-refresher [enabled]="notesLoaded" (ionRefresh)="refreshNotes(false, $event)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<core-loading [hideUntil]="notesLoaded" class="core-loading-center">
<p class="core-warning-card" *ngIf="hasOffline">
<ion-icon name="alert" color="warning" padding-right></ion-icon> {{ 'core.thereisdatatosync' | translate:{$a: 'addon.notes.notes' | translate | lowercase } }}
</p>
<core-empty-box *ngIf="notes && notes.length == 0" icon="list" [message]="'addon.notes.nonotes' | translate"></core-empty-box>
<ion-list *ngIf="notes && notes.length > 0">
<ion-card *ngFor="let note of notes">
<ion-item text-wrap>
<ion-avatar item-start>
<img [src]="note.userprofileimageurl || 'assets/img/user-avatar.png'" core-external-content (click)="openUserProfile(note.userid)" [alt]="'core.pictureof' | translate:{$a: note.userfullname}" role="presentation">
</ion-avatar>
<h2>{{note.userfullname}}</h2>
<p *ngIf="!note.offline" item-end>{{note.lastmodified | coreDateDayOrTime}}</p>
<p *ngIf="note.offline" item-end><ion-icon name="clock"></ion-icon> {{ 'core.notsent' | translate }}</p>
</ion-item>
<ion-item text-wrap>
<core-format-text [clean]="true" [text]="note.content"></core-format-text>
</ion-item>
</ion-card>
</ion-list>
</core-loading>
</ion-content>

View File

@ -0,0 +1,35 @@
// (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 { CorePipesModule } from '@pipes/pipes.module';
import { AddonNotesListPage } from './list';
@NgModule({
declarations: [
AddonNotesListPage,
],
imports: [
CoreComponentsModule,
CoreDirectivesModule,
CorePipesModule,
IonicPageModule.forChild(AddonNotesListPage),
TranslateModule.forChild()
],
})
export class AddonNotesListPageModule {}

View File

@ -0,0 +1,173 @@
// (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, OnDestroy, Optional, ViewChild } from '@angular/core';
import { Content, IonicPage, NavController, NavParams } from 'ionic-angular';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider } from '@providers/sites';
import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { AddonNotesProvider } from '../../providers/notes';
import { AddonNotesSyncProvider } from '../../providers/notes-sync';
/**
* Page that displays the list of notes.
*/
@IonicPage({ segment: 'addon-notes-list' })
@Component({
selector: 'page-addon-notes-list',
templateUrl: 'list.html',
})
export class AddonNotesListPage implements OnDestroy {
@ViewChild(Content) content: Content;
protected courseId = 0;
protected syncObserver: any;
type = '';
refreshIcon = 'spinner';
syncIcon = 'spinner';
notes: any[];
hasOffline = false;
notesLoaded = false;
constructor(navParams: NavParams, private navCtrl: NavController, @Optional() private svComponent: CoreSplitViewComponent,
private domUtils: CoreDomUtilsProvider, private textUtils: CoreTextUtilsProvider,
sitesProvider: CoreSitesProvider, eventsProvider: CoreEventsProvider,
private notesProvider: AddonNotesProvider, private notesSync: AddonNotesSyncProvider) {
this.courseId = navParams.get('courseId') || sitesProvider.getCurrentSiteHomeId();
this.type = navParams.get('type');
// Refresh data if notes are synchronized automatically.
this.syncObserver = eventsProvider.on(AddonNotesSyncProvider.AUTO_SYNCED, (data) => {
if (data.courseId == this.courseId) {
// Show the sync warnings.
this.showSyncWarnings(data.warnings);
// Refresh the data.
this.notesLoaded = false;
this.refreshIcon = 'spinner';
this.syncIcon = 'spinner';
this.content.scrollToTop();
this.fetchNotes(false);
}
}, sitesProvider.getCurrentSiteId());
}
/**
* View loaded.
*/
ionViewDidLoad(): void {
this.fetchNotes(true).then(() => {
this.notesProvider.logView(this.courseId);
});
}
/**
* Fetch notes
* @param {boolean} sync When to resync notes.
* @param {boolean} [showErrors] When to display errors or not.
* @return {Promise<any>} Promise with the notes,
*/
private fetchNotes(sync: boolean, showErrors?: boolean): Promise<any> {
const promise = sync ? this.syncNotes(showErrors) : Promise.resolve();
return promise.catch(() => {
// Ignore errors.
}).then(() => {
return this.notesProvider.getNotes(this.courseId).then((notes) => {
notes = notes[this.type + 'notes'] || [];
this.hasOffline = this.notesProvider.hasOfflineNote(notes);
return this.notesProvider.getNotesUserData(notes, this.courseId).then((notes) => {
this.notes = notes;
});
});
}).catch((message) => {
this.domUtils.showErrorModal(message);
}).finally(() => {
this.notesLoaded = true;
this.refreshIcon = 'refresh';
this.syncIcon = 'loop';
});
}
/**
* Refresh notes on PTR.
*
* @param {boolean} showErrors Whether to display errors or not.
* @param {any} refresher Refresher instance.
*/
refreshNotes(showErrors: boolean, refresher?: any): void {
this.refreshIcon = 'spinner';
this.syncIcon = 'spinner';
this.notesProvider.invalidateNotes(this.courseId).finally(() => {
this.fetchNotes(true, showErrors).finally(() => {
if (refresher) {
refresher.complete();
}
});
});
}
/**
* Tries to syncrhonize course notes.
*
* @param {boolean} showErrors Whether to display errors or not.
* @return {Promise<any>} Promise resolved if sync is successful, rejected otherwise.
*/
private syncNotes(showErrors: boolean): Promise<any> {
return this.notesSync.syncNotes(this.courseId).then((warnings) => {
this.showSyncWarnings(warnings);
}).catch((error) => {
if (showErrors) {
this.domUtils.showErrorModalDefault(error, 'core.errorsync', true);
}
return Promise.reject(null);
});
}
/**
* Show sync warnings if any.
*
* @param {string[]} warnings the warnings
*/
private showSyncWarnings(warnings: string[]): void {
const message = this.textUtils.buildMessage(warnings);
if (message) {
this.domUtils.showErrorModal(message);
}
}
/**
* Opens the profile of a user.
*
* @param {number} userId
*/
openUserProfile(userId: number): void {
// Decide which navCtrl to use. If this page is inside a split view, use the split view's master nav.
const navCtrl = this.svComponent ? this.svComponent.getMasterNav() : this.navCtrl;
navCtrl.push('CoreUserProfilePage', {userId, courseId: this.courseId});
}
/**
* Page destroyed.
*/
ngOnDestroy(): void {
this.syncObserver && this.syncObserver.off();
}
}

View File

@ -0,0 +1,90 @@
// (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, Injector } from '@angular/core';
import { AddonNotesProvider } from './notes';
import { CoreCourseProvider } from '@core/course/providers/course';
import { CoreCourseOptionsHandler, CoreCourseOptionsHandlerData } from '@core/course/providers/options-delegate';
import { AddonNotesTypesComponent } from '../components/types/types';
/**
* Handler to inject an option into the course main menu.
*/
@Injectable()
export class AddonNotesCourseOptionHandler implements CoreCourseOptionsHandler {
name = 'AddonNotes';
priority = 200;
protected coursesNavEnabledCache = {};
constructor(private notesProvider: AddonNotesProvider) {
}
/**
* Clear courses nav cache.
*/
clearCoursesNavCache(): void {
this.coursesNavEnabledCache = {};
}
/**
* Whether or not the handler is enabled on a site level.
* @return {boolean|Promise<boolean>} Whether or not the handler is enabled on a site level.
*/
isEnabled(): boolean | Promise<boolean> {
return this.notesProvider.isPluginViewNotesEnabled();
}
/**
* Whether or not the handler is enabled for a certain course.
*
* @param {number} courseId The course ID.
* @param {any} accessData Access type and data. Default, guest, ...
* @param {any} [navOptions] Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions.
* @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
* @return {boolean|Promise<boolean>} True or promise resolved with true if enabled.
*/
isEnabledForCourse(courseId: number, accessData: any, navOptions?: any, admOptions?: any): boolean | Promise<boolean> {
if (accessData && accessData.type == CoreCourseProvider.ACCESS_GUEST) {
return false; // Not enabled for guests.
}
if (navOptions && typeof navOptions.notes != 'undefined') {
return navOptions.notes;
}
if (typeof this.coursesNavEnabledCache[courseId] != 'undefined') {
return this.coursesNavEnabledCache[courseId];
}
return this.notesProvider.isPluginViewNotesEnabledForCourse(courseId).then((enabled) => {
this.coursesNavEnabledCache[courseId] = enabled;
return enabled;
});
}
/**
* Returns the data needed to render the handler.
*
* @param {number} courseId The course ID.
* @return {CoreCourseOptionsHandlerData} Data.
*/
getDisplayData?(injector: Injector, courseId: number): CoreCourseOptionsHandlerData {
return {
title: 'addon.notes.notes',
class: 'addon-notes-course-handler',
component: AddonNotesTypesComponent,
};
}
}

View File

@ -0,0 +1,232 @@
// (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 { CoreTimeUtilsProvider } from '@providers/utils/time';
/**
* Service to handle offline notes.
*/
@Injectable()
export class AddonNotesOfflineProvider {
protected logger;
// Variables for database.
protected NOTES_TABLE = 'addon_notes_offline_notes';
protected tablesSchema = [
{
name: this.NOTES_TABLE,
columns: [
{
name: 'userid',
type: 'INTEGER'
},
{
name: 'courseid',
type: 'INTEGER'
},
{
name: 'publishstate',
type: 'TEXT',
},
{
name: 'content',
type: 'TEXT'
},
{
name: 'format',
type: 'INTEGER'
},
{
name: 'created',
type: 'INTEGER'
},
{
name: 'lastmodified',
type: 'INTEGER'
}
],
primaryKeys: ['userid', 'content', 'created']
}
];
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private timeUtils: CoreTimeUtilsProvider) {
this.logger = logger.getInstance('AddonNotesOfflineProvider');
this.sitesProvider.createTablesFromSchema(this.tablesSchema);
}
/**
* Delete a note.
*
* @param {number} userId User ID the note is about.
* @param {string} content The note content.
* @param {number} timecreated The time the note was created.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved if deleted, rejected if failure.
*/
deleteNote(userId: number, content: string, timecreated: number, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
return site.getDb().deleteRecords(this.NOTES_TABLE, {
userid: userId,
content: content,
created: timecreated
});
});
}
/**
* Get all offline notes.
*
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved with notes.
*/
getAllNotes(siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
return site.getDb().getRecords(this.NOTES_TABLE);
});
}
/**
* Get an offline note.
*
* @param {number} userId User ID the note is about.
* @param {string} content The note content.
* @param {number} timecreated The time the note was created.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved with the notes.
*/
getNote(userId: number, content: string, timecreated: number, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
return site.getDb().getRecord(this.NOTES_TABLE, {
userid: userId,
content: content,
created: timecreated
});
});
}
/**
* Get offline notes for a certain course.
*
* @param {number} courseId Course ID.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any[]>} Promise resolved with notes.
*/
getNotesForCourse(courseId: number, siteId?: string): Promise<any[]> {
return this.sitesProvider.getSite(siteId).then((site) => {
return site.getDb().getRecords(this.NOTES_TABLE, {courseid: courseId});
});
}
/**
* Get offline notes for a certain user.
*
* @param {number} userId User ID the notes are about.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any[]>} Promise resolved with notes.
*/
getNotesForUser(userId: number, siteId?: string): Promise<any[]> {
return this.sitesProvider.getSite(siteId).then((site) => {
return site.getDb().getRecords(this.NOTES_TABLE, {userid: userId});
});
}
/**
* Get offline notes with a certain publish state (Personal, Site or Course).
*
* @param {string} state Publish state ('personal', 'site' or 'course').
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any[]>} Promise resolved with notes.
*/
getNotesWithPublishState(state: string, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
return site.getDb().getRecords(this.NOTES_TABLE, {publishstate: state});
});
}
/**
* Check if there are offline notes for a certain course.
*
* @param {number} courseId Course ID.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<boolean>} Promise resolved with boolean: true if has offline notes, false otherwise.
*/
hasNotesForCourse(courseId: number, siteId?: string): Promise<boolean> {
return this.getNotesForCourse(courseId, siteId).then((notes) => {
return !!notes.length;
});
}
/**
* Check if there are offline notes for a certain user.
*
* @param {number} userId User ID the notes are about.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<boolean>} Promise resolved with boolean: true if has offline notes, false otherwise.
*/
hasNotesForUser(userId: number, siteId?: string): Promise<boolean> {
return this.getNotesForUser(userId, siteId).then((notes) => {
return !!notes.length;
});
}
/**
* Check if there are offline notes with a certain publish state (Personal, Site or Course).
*
* @param {string} state Publish state ('personal', 'site' or 'course').
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<boolean>} Promise resolved with boolean: true if has offline notes, false otherwise.
*/
hasNotesWithPublishState(state: string, siteId?: string): Promise<boolean> {
return this.getNotesWithPublishState(state, siteId).then((notes) => {
return !!notes.length;
});
}
/**
* Save a note to be sent later.
*
* @param {number} userId User ID the note is about.
* @param {number} courseId Course ID.
* @param {string} state Publish state ('personal', 'site' or 'course').
* @param {string} content The note content.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved if stored, rejected if failure.
*/
saveNote(userId: number, courseId: number, state: string, content: string, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
const now = this.timeUtils.timestamp();
const data = {
userid: userId,
courseid: courseId,
publishstate: state,
content: content,
format: 1,
created: now,
lastmodified: now
};
const conditions = {
userid: userId,
content: content,
created: now
};
return site.getDb().insertOrUpdateRecord(this.NOTES_TABLE, data, conditions).then(() => {
return data;
});
});
}
}

View File

@ -0,0 +1,198 @@
// (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 { AddonNotesOfflineProvider } from './notes-offline';
import { AddonNotesProvider } from './notes';
import { CoreCoursesProvider } from '@core/courses/providers/courses';
import { CoreEventsProvider } from '@providers/events';
import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { TranslateService } from '@ngx-translate/core';
import { CoreSyncProvider } from '@providers/sync';
/**
* Service to sync notes.
*/
@Injectable()
export class AddonNotesSyncProvider extends CoreSyncBaseProvider {
static AUTO_SYNCED = 'addon_notes_autom_synced';
constructor(protected sitesProvider: CoreSitesProvider, protected loggerProvider: CoreLoggerProvider,
protected appProvider: CoreAppProvider, private notesOffline: AddonNotesOfflineProvider,
private eventsProvider: CoreEventsProvider, private notesProvider: AddonNotesProvider,
private coursesProvider: CoreCoursesProvider, private translate: TranslateService, private utils: CoreUtilsProvider,
syncProvider: CoreSyncProvider, protected textUtils: CoreTextUtilsProvider) {
super('AddonNotesSync', sitesProvider, loggerProvider, appProvider, syncProvider, textUtils);
}
/**
* Try to synchronize all the notes 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.
*/
syncAllNotes(siteId?: string): Promise<any> {
return this.syncOnSites('all notes', this.syncAllNotesFunc.bind(this), [], siteId);
}
/**
* Synchronize all the notes in a certain site
*
* @param {string} siteId Site ID to sync.
* @return {Promise<any>} Promise resolved if sync is successful, rejected if sync fails.
*/
private syncAllNotesFunc(siteId: string): Promise<any> {
return this.notesOffline.getAllNotes(siteId).then((notes) => {
// Get all the courses to be synced.
const courseIds = [];
notes.forEach((note) => {
if (courseIds.indexOf(note.courseid) == -1) {
courseIds.push(note.courseid);
}
});
// Sync all courses.
const promises = courseIds.map((courseId) => {
return this.syncNotesIfNeeded(courseId, siteId).then((warnings) => {
if (typeof warnings != 'undefined') {
// Sync successful, send event.
this.eventsProvider.trigger(AddonNotesSyncProvider.AUTO_SYNCED, {
courseId: courseId,
warnings: warnings
}, siteId);
}
});
});
return Promise.all(promises);
});
}
/**
* Sync course notes only if a certain time has passed since the last time.
*
* @param {number} courseId Course ID.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the notes are synced or if they don't need to be synced.
*/
private syncNotesIfNeeded(courseId: number, siteId?: string): Promise<void> {
return this.isSyncNeeded(courseId, siteId).then((needed) => {
if (needed) {
return this.syncNotes(courseId, siteId);
}
});
}
/**
* Synchronize notes of a course.
*
* @param {number} courseId Course ID.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved if sync is successful, rejected otherwise.
*/
syncNotes(courseId: number, siteId?: string): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
if (this.isSyncing(courseId, siteId)) {
// There's already a sync ongoing for notes, return the promise.
return this.getOngoingSync(courseId, siteId);
}
this.logger.debug('Try to sync notes for course ' + courseId);
const warnings = [];
// Get offline notes to be sent.
const syncPromise = this.notesOffline.getNotesForCourse(courseId, siteId).then((notes) => {
if (!notes.length) {
// Nothing to sync.
return;
} else if (!this.appProvider.isOnline()) {
// Cannot sync in offline.
return Promise.reject(this.translate.instant('core.networkerrormsg'));
}
const errors = [];
// Format the notes to be sent.
const notesToSend = notes.map((note) => {
return {
userid: note.userid,
publishstate: note.publishstate,
courseid: note.courseid,
text: note.content,
format: note.format
};
});
// Send the notes.
return this.notesProvider.addNotesOnline(notesToSend, siteId).then((response) => {
// Search errors in the response.
response.forEach((entry) => {
if (entry.noteid === -1 && errors.indexOf(entry.errormessage) == -1) {
errors.push(entry.errormessage);
}
});
// Fetch the notes from server to be sure they're up to date.
return this.notesProvider.invalidateNotes(courseId, siteId).then(() => {
return this.notesProvider.getNotes(courseId, false, true, siteId);
}).catch(() => {
// Ignore errors.
});
}).catch((error) => {
if (this.utils.isWebServiceError(error)) {
// It's a WebService error, this means the user cannot send notes.
errors.push(error);
} else {
// Not a WebService error, reject the synchronization to try again.
return Promise.reject(error);
}
}).then(() => {
// Notes were sent, delete them from local DB.
const promises = notes.map((note) => {
return this.notesOffline.deleteNote(note.userid, note.content, note.created, siteId);
});
return Promise.all(promises);
}).then(() => {
if (errors && errors.length) {
// At least an error occurred, get course name and add errors to warnings array.
return this.coursesProvider.getUserCourse(courseId, true, siteId).catch(() => {
// Ignore errors.
return {};
}).then((course) => {
errors.forEach((error) => {
warnings.push(this.translate.instant('addon.notes.warningnotenotsent', {
course: course.fullname ? course.fullname : courseId,
error: error
}));
});
});
}
});
}).then(() => {
// All done, return the warnings.
return warnings;
});
return this.addOngoingSync(courseId, syncPromise, siteId);
}
}

View File

@ -0,0 +1,367 @@
// (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 { CoreAppProvider } from '@providers/app';
import { CoreLoggerProvider } from '@providers/logger';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreSitesProvider } from '@providers/sites';
import { CoreSiteWSPreSets } from '@classes/site';
import { TranslateService } from '@ngx-translate/core';
import { CoreUserProvider } from '@core/user/providers/user';
import { AddonNotesOfflineProvider } from './notes-offline';
/**
* Service to handle notes.
*/
@Injectable()
export class AddonNotesProvider {
protected ROOT_CACHE_KEY = 'mmaNotes:';
protected logger;
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private appProvider: CoreAppProvider,
private utilsProvider: CoreUtilsProvider, private translate: TranslateService, private userProvider: CoreUserProvider,
private notesOffline: AddonNotesOfflineProvider) {
this.logger = logger.getInstance('AddonNotesProvider');
}
/**
* Add a note.
*
* @param {number} userId User ID of the person to add the note.
* @param {number} courseId Course ID where the note belongs.
* @param {string} publishState Personal, Site or Course.
* @param {string} noteText The note text.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<boolean>} Promise resolved with boolean: true if note was sent to server, false if stored in device.
*/
addNote(userId: number, courseId: number, publishState: string, noteText: string, siteId?: string): Promise<boolean> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
// Convenience function to store a note to be synchronized later.
const storeOffline = (): Promise<any> => {
return this.notesOffline.saveNote(userId, courseId, publishState, noteText, siteId).then(() => {
return false;
});
};
if (!this.appProvider.isOnline()) {
// App is offline, store the note.
return storeOffline();
}
// Send note to server.
return this.addNoteOnline(userId, courseId, publishState, noteText, siteId).then(() => {
return true;
}).catch((data) => {
if (data.wserror) {
// It's a WebService error, the user cannot add the note so don't store it.
return Promise.reject(data.error);
} else {
// Error sending note, store it to retry later.
return storeOffline();
}
});
}
/**
* Add a note. It will fail if offline or cannot connect.
*
* @param {number} userId User ID of the person to add the note.
* @param {number} courseId Course ID where the note belongs.
* @param {string} publishState Personal, Site or Course.
* @param {string} noteText The note text.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when added, rejected otherwise. Reject param is an object with:
* - error: The error message.
* - wserror: True if it's an error returned by the WebService, false otherwise.
*/
addNoteOnline(userId: number, courseId: number, publishState: string, noteText: string, siteId?: string): Promise<any> {
const notes = [
{
courseid: courseId,
format: 1,
publishstate: publishState,
text: noteText,
userid: userId
}
];
return this.addNotesOnline(notes, siteId).catch((error) => {
return Promise.reject({
error: error,
wserror: this.utilsProvider.isWebServiceError(error)
});
}).then((response) => {
if (response && response[0] && response[0].noteid === -1) {
// There was an error, and it should be translated already.
return Promise.reject({
error: response[0].errormessage,
wserror: true
});
}
// A note was added, invalidate the course notes.
return this.invalidateNotes(courseId, siteId).catch(() => {
// Ignore errors.
});
});
}
/**
* Add several notes. It will fail if offline or cannot connect.
*
* @param {any[]} notes Notes to save.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when added, rejected otherwise. Promise resolved doesn't mean that notes
* have been added, the resolve param can contain errors for notes not sent.
*/
addNotesOnline(notes: any[], siteId?: string): Promise<any> {
if (!notes || !notes.length) {
return Promise.resolve();
}
return this.sitesProvider.getSite(siteId).then((site) => {
const data = {
notes: notes
};
return site.write('core_notes_create_notes', data);
});
}
/**
* Returns whether or not the add note 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 {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<boolean>} Promise resolved with true if enabled, resolved with false or rejected otherwise.
*/
isPluginAddNoteEnabled(siteId?: string): Promise<boolean> {
return this.sitesProvider.getSite(siteId).then((site) => {
if (!site.canUseAdvancedFeature('enablenotes')) {
return false;
} else if (!site.wsAvailable('core_notes_create_notes')) {
return false;
}
return true;
});
}
/**
* Returns whether or not the add note plugin is enabled for a certain course.
*
* @param {number} courseId ID of the course.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<boolean>} Promise resolved with true if enabled, resolved with false or rejected otherwise.
*/
isPluginAddNoteEnabledForCourse(courseId: number, siteId?: string): Promise<boolean> {
return this.sitesProvider.getSite(siteId).then((site) => {
// The only way to detect if it's enabled is to perform a WS call.
// We use an invalid user ID (-1) to avoid saving the note if the user has permissions.
const data = {
notes: [
{
userid: -1,
publishstate: 'personal',
courseid: courseId,
text: '',
format: 1
}
]
};
/* Use .read to cache data and be able to check it in offline. This means that, if a user loses the capabilities
to add notes, he'll still see the option in the app. */
return site.read('core_notes_create_notes', data).then(() => {
// User can add notes.
return true;
}).catch(() => {
return false;
});
});
}
/**
* Returns whether or not the read notes plugin is enabled for the current site.
*
* This method is called quite often and thus should only perform a quick
* check, we should not be calling WS from here.
*
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<boolean>} Promise resolved with true if enabled, resolved with false or rejected otherwise.
*/
isPluginViewNotesEnabled(siteId?: string): Promise<boolean> {
return this.sitesProvider.getSite(siteId).then((site) => {
if (!site.canUseAdvancedFeature('enablenotes')) {
return false;
} else if (!site.wsAvailable('core_notes_get_course_notes')) {
return false;
}
return true;
});
}
/**
* Returns whether or not the read notes plugin is enabled for a certain course.
*
* @param {number} courseId ID of the course.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<boolean>} Promise resolved with true if enabled, resolved with false or rejected otherwise.
*/
isPluginViewNotesEnabledForCourse(courseId: number, siteId?: string): Promise<boolean> {
return this.getNotes(courseId, false, true, siteId).then(() => {
return true;
}).catch(() => {
return false;
});
}
/**
* Get the cache key for the get notes call.
*
* @param {number} courseId ID of the course to get the notes from.
* @return {string} Cache key.
*/
getNotesCacheKey(courseId: number): string {
return this.ROOT_CACHE_KEY + 'notes:' + courseId;
}
/**
* Get users notes for a certain site, course and personal notes.
*
* @param {number} courseId ID of the course to get the notes from.
* @param {boolean} [ignoreCache] True when we should not get the value from the cache.
* @param {boolean} [onlyOnline] True to return only online notes, false to return both online and offline.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise to be resolved when the notes are retrieved.
*/
getNotes(courseId: number, ignoreCache?: boolean, onlyOnline?: boolean, siteId?: string): Promise<any> {
this.logger.debug('Get notes for course ' + courseId);
return this.sitesProvider.getSite(siteId).then((site) => {
const data = {
courseid: courseId
};
const preSets: CoreSiteWSPreSets = {
cacheKey: this.getNotesCacheKey(courseId)
};
if (ignoreCache) {
preSets.getFromCache = false;
preSets.emergencyCache = false;
}
return site.read('core_notes_get_course_notes', data, preSets).then((notes) => {
if (onlyOnline) {
return notes;
}
// Get offline notes and add them to the list.
return this.notesOffline.getNotesForCourse(courseId, siteId).then((offlineNotes) => {
offlineNotes.forEach((note) => {
const fieldName = note.publishstate + 'notes';
if (!notes[fieldName]) {
notes[fieldName] = [];
}
note.offline = true;
// Add note to the start of array since last notes are shown first.
notes[fieldName].unshift(note);
});
return notes;
});
});
});
}
/**
* Get user data for notes since they only have userid.
*
* @param {any[]} notes Notes to get the data for.
* @param {number} courseId ID of the course the notes belong to.
* @return {Promise<any>} Promise always resolved. Resolve param is the formatted notes.
*/
getNotesUserData(notes: any[], courseId: number): Promise<any> {
const promises = notes.map((note) => {
// Get the user profile to retrieve the user image.
return this.userProvider.getProfile(note.userid, note.courseid, true).then((user) => {
note.userfullname = user.fullname;
note.userprofileimageurl = user.profileimageurl || null;
}).catch(() => {
note.userfullname = this.translate.instant('addon.notes.userwithid', {id: note.userid});
});
});
return Promise.all(promises).then(() => {
return notes;
});
}
/**
* Given a list of notes, check if any of them is an offline note.
*
* @param {any[]} notes List of notes.
* @return {boolean} True if at least 1 note is offline, false otherwise.
*/
hasOfflineNote(notes: any[]): boolean {
if (!notes || !notes.length) {
return false;
}
for (let i = 0, len = notes.length; i < len; i++) {
if (notes[i].offline) {
return true;
}
}
return false;
}
/**
* Invalidate get notes WS call.
*
* @param {number} courseId Course ID.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when data is invalidated.
*/
invalidateNotes(courseId: number, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
return site.invalidateWsCacheForKey(this.getNotesCacheKey(courseId));
});
}
/**
* Report notes as being viewed.
*
* @param {number} courseId ID of the course.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the WS call is successful.
*/
logView(courseId: number, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
const params = {
courseid: courseId,
userid: 0
};
return site.write('core_notes_view_notes', params);
});
}
}

View File

@ -0,0 +1,47 @@
// (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 { AddonNotesSyncProvider } from './notes-sync';
/**
* Synchronization cron handler.
*/
@Injectable()
export class AddonNotesSyncCronHandler implements CoreCronHandler {
name = 'AddonNotesSyncCronHandler';
constructor(private notesSync: AddonNotesSyncProvider) {}
/**
* 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.
* @return {Promise<any>} Promise resolved when done, rejected if failure.
*/
execute(siteId?: string): Promise<any> {
return this.notesSync.syncAllNotes(siteId);
}
/**
* Get the time between consecutive executions.
*
* @return {number} Time between consecutive executions (in ms).
*/
getInterval(): number {
return 600000; // 10 minutes.
}
}

View File

@ -0,0 +1,101 @@
// (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 { ModalController } from 'ionic-angular';
import { CoreUserDelegate, CoreUserProfileHandler, CoreUserProfileHandlerData } from '@core/user/providers/user-delegate';
import { CoreSitesProvider } from '@providers/sites';
import { AddonNotesProvider } from './notes';
/**
* Profile notes handler.
*/
@Injectable()
export class AddonNotesUserHandler implements CoreUserProfileHandler {
name = 'AddonNotes';
priority = 200;
type = CoreUserDelegate.TYPE_COMMUNICATION;
addNoteEnabledCache = {};
constructor(private modalCtrl: ModalController, private sitesProvider: CoreSitesProvider,
private notesProvider: AddonNotesProvider) {
}
/**
* Clear add note cache.
* If a courseId is specified, it will only delete the entry for that course.
*
* @param {number} [courseId] Course ID.
*/
clearAddNoteCache(courseId?: number): void {
if (courseId) {
delete this.addNoteEnabledCache[courseId];
} else {
this.addNoteEnabledCache = {};
}
}
/**
* Whether or not the handler is enabled on a site level.
* @return {boolean|Promise<boolean>} Whether or not the handler is enabled on a site level.
*/
isEnabled(): boolean | Promise<boolean> {
return this.notesProvider.isPluginAddNoteEnabled();
}
/**
* Check if handler is enabled for this user in this context.
*
* @param {any} user User to check.
* @param {number} courseId Course ID.
* @param {any} [navOptions] Course navigation options for current user. See $mmCourses#getUserNavigationOptions.
* @param {any} [admOptions] Course admin options for current user. See $mmCourses#getUserAdministrationOptions.
* @return {boolean|Promise<boolean>} Promise resolved with true if enabled, resolved with false otherwise.
*/
isEnabledForUser(user: any, courseId: number, navOptions?: any, admOptions?: any): boolean | Promise<boolean> {
// Active course required.
if (!courseId || user.id == this.sitesProvider.getCurrentSiteUserId()) {
return Promise.resolve(false);
}
if (typeof this.addNoteEnabledCache[courseId] != 'undefined') {
return this.addNoteEnabledCache[courseId];
}
return this.notesProvider.isPluginAddNoteEnabledForCourse(courseId).then((enabled) => {
this.addNoteEnabledCache[courseId] = enabled;
return enabled;
});
}
/**
* Returns the data needed to render the handler.
*
* @return {CoreUserProfileHandlerData} Data needed to render the handler.
*/
getDisplayData(user: any, courseId: number): CoreUserProfileHandlerData {
return {
icon: 'list',
title: 'addon.notes.addnewnote',
class: 'addon-notes-handler',
action: (event, navCtrl, user, courseId): void => {
event.preventDefault();
event.stopPropagation();
const modal = this.modalCtrl.create('AddonNotesAddPage', { userId: user.id, courseId });
modal.present();
}
};
}
}

View File

@ -79,6 +79,7 @@ import { AddonModLabelModule } from '@addon/mod/label/label.module';
import { AddonModResourceModule } from '@addon/mod/resource/resource.module';
import { AddonModFolderModule } from '@addon/mod/folder/folder.module';
import { AddonMessagesModule } from '@addon/messages/messages.module';
import { AddonNotesModule } from '../addon/notes/notes.module';
import { AddonPushNotificationsModule } from '@addon/pushnotifications/pushnotifications.module';
import { AddonRemoteThemesModule } from '@addon/remotethemes/remotethemes.module';
@ -161,6 +162,7 @@ export const CORE_PROVIDERS: any[] = [
AddonModResourceModule,
AddonModFolderModule,
AddonMessagesModule,
AddonNotesModule,
AddonPushNotificationsModule,
AddonRemoteThemesModule
],

View File

@ -151,11 +151,11 @@ export class CoreSyncBaseProvider {
/**
* Check if a sync is needed: if a certain time has passed since the last time.
*
* @param {string} id Unique sync identifier per component.
* @param {string | number} id Unique sync identifier per component.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<boolean>} Promise resolved with boolean: whether sync is needed.
*/
isSyncNeeded(id: string, siteId?: string): Promise<boolean> {
isSyncNeeded(id: string | number, siteId?: string): Promise<boolean> {
return this.getSyncTime(id, siteId).then((time) => {
return Date.now() - this.syncInterval >= time;
});
@ -178,12 +178,12 @@ export class CoreSyncBaseProvider {
/**
* Set the synchronization warnings.
*
* @param {string} id Unique sync identifier per component.
* @param {string | number} id Unique sync identifier per component.
* @param {string[]} warnings Warnings to set.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when done.
*/
setSyncWarnings(id: string, warnings: string[], siteId?: string): Promise<any> {
setSyncWarnings(id: string | number, warnings: string[], siteId?: string): Promise<any> {
const warningsText = JSON.stringify(warnings || []);
return this.syncProvider.insertOrUpdateSyncRecord(this.component, id, { warnings: warningsText }, siteId);
@ -245,5 +245,5 @@ export class CoreSyncBaseProvider {
}
return Promise.resolve();
}
}
}