MOBILE-1927 calendar: Allow creating events in online
This commit is contained in:
		
							parent
							
								
									f7ea003b41
								
							
						
					
					
						commit
						cfaaefc184
					
				| @ -3,13 +3,26 @@ | ||||
|     "calendarevents": "Calendar events", | ||||
|     "calendarreminders": "Calendar reminders", | ||||
|     "defaultnotificationtime": "Default notification time", | ||||
|     "durationminutes": "Duration in minutes", | ||||
|     "durationnone": "Without duration", | ||||
|     "durationuntil": "Until", | ||||
|     "editevent": "Editing event", | ||||
|     "errorloadevent": "Error loading event.", | ||||
|     "errorloadevents": "Error loading events.", | ||||
|     "eventduration": "Duration", | ||||
|     "eventendtime": "End time", | ||||
|     "eventname": "Event title", | ||||
|     "eventstarttime": "Start time", | ||||
|     "eventtype": "Event type", | ||||
|     "gotoactivity": "Go to activity", | ||||
|     "invalidtimedurationminutes": "The duration in minutes you have entered is invalid. Please enter the duration in minutes greater than 0 or select no duration.", | ||||
|     "invalidtimedurationuntil": "The date and time you selected for duration until is before the start time of the event. Please correct this before proceeding.", | ||||
|     "newevent": "New event", | ||||
|     "noevents": "There are no events", | ||||
|     "nopermissiontoupdatecalendar": "Sorry, but you do not have permission to update the calendar event", | ||||
|     "reminders": "Reminders", | ||||
|     "repeatevent": "Repeat this event", | ||||
|     "repeatweeksl": "Repeat weekly, creating altogether", | ||||
|     "setnewreminder": "Set a new reminder", | ||||
|     "typeclose": "Close event", | ||||
|     "typecourse": "Course event", | ||||
|  | ||||
							
								
								
									
										144
									
								
								src/addon/calendar/pages/edit-event/edit-event.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								src/addon/calendar/pages/edit-event/edit-event.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,144 @@ | ||||
| <ion-header> | ||||
|     <ion-navbar core-back-button> | ||||
|         <ion-title><core-format-text [text]="title | translate"></core-format-text></ion-title> | ||||
|     </ion-navbar> | ||||
| </ion-header> | ||||
| <ion-content> | ||||
|     <ion-refresher [enabled]="loaded" (ionRefresh)="refreshData($event)"> | ||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|     </ion-refresher> | ||||
| 
 | ||||
|     <core-loading [hideUntil]="loaded"> | ||||
|         <form ion-list [formGroup]="eventForm"> | ||||
|             <!-- Event name. --> | ||||
|             <ion-item text-wrap> | ||||
|                 <ion-label stacked><h2 [core-mark-required]="true">{{ 'addon.calendar.eventname' | translate }}</h2></ion-label> | ||||
|                 <ion-input type="text" name="name" [placeholder]="'addon.calendar.eventname' | translate" [formControlName]="'name'"></ion-input> | ||||
|                 <core-input-errors item-content [control]="eventForm.controls.name" [errorMessages]="errors"></core-input-errors> | ||||
|             </ion-item> | ||||
| 
 | ||||
|             <!-- Date. --> | ||||
|             <ion-item text-wrap> | ||||
|                 <ion-label stacked><h2 [core-mark-required]="true">{{ 'core.date' | translate }}</h2></ion-label> | ||||
|                 <ion-datetime [formControlName]="'timestart'" [placeholder]="'core.date' | translate" [displayFormat]="dateFormat"></ion-datetime> | ||||
|                 <core-input-errors item-content [control]="eventForm.controls.timestart" [errorMessages]="errors"></core-input-errors> | ||||
|             </ion-item> | ||||
| 
 | ||||
|             <!-- Type. --> | ||||
|             <ion-item text-wrap class="addon-calendar-eventtype-container"> | ||||
|                 <ion-label id="addon-calendar-eventtype-label"><h2 [core-mark-required]="true">{{ 'addon.calendar.eventtype' | translate }}</h2></ion-label> | ||||
|                 <ion-select [formControlName]="'eventtype'" aria-labelledby="addon-calendar-eventtype-label" interface="action-sheet" [disabled]="eventTypes.length == 1"> | ||||
|                     <ion-option *ngFor="let type of eventTypes" [value]="type.value">{{ type.name | translate }}</ion-option> | ||||
|                 </ion-select> | ||||
|             </ion-item> | ||||
| 
 | ||||
|             <!-- Category. --> | ||||
|             <ion-item text-wrap *ngIf="eventTypeControl.value == 'category'"> | ||||
|                 <ion-label id="addon-calendar-category-label"><h2 [core-mark-required]="true">{{ 'core.category' | translate }}</h2></ion-label> | ||||
|                 <ion-select [formControlName]="'categoryid'" aria-labelledby="addon-calendar-category-label" interface="action-sheet" [placeholder]="'core.noselection' | translate"> | ||||
|                     <ion-option *ngFor="let category of categories" [value]="category.id">{{ category.name }}</ion-option> | ||||
|                 </ion-select> | ||||
|             </ion-item> | ||||
| 
 | ||||
|             <!-- Course. --> | ||||
|             <ion-item text-wrap *ngIf="eventTypeControl.value == 'course'"> | ||||
|                 <ion-label id="addon-calendar-course-label"><h2 [core-mark-required]="true">{{ 'core.course' | translate }}</h2></ion-label> | ||||
|                 <ion-select [formControlName]="'courseid'" aria-labelledby="addon-calendar-course-label" interface="action-sheet" [placeholder]="'core.noselection' | translate"> | ||||
|                     <ion-option *ngFor="let course of courses" [value]="course.id">{{ course.fullname }}</ion-option> | ||||
|                 </ion-select> | ||||
|             </ion-item> | ||||
| 
 | ||||
|             <!-- Group. --> | ||||
|             <ng-container *ngIf="eventTypeControl.value == 'group'"> | ||||
|                 <!-- Select the course. --> | ||||
|                 <ion-item text-wrap> | ||||
|                     <ion-label id="addon-calendar-groupcourse-label"><h2 [core-mark-required]="true">{{ 'core.course' | translate }}</h2></ion-label> | ||||
|                     <ion-select [formControlName]="'groupcourseid'" aria-labelledby="addon-calendar-groupcourse-label" interface="action-sheet" [placeholder]="'core.noselection' | translate" (ionChange)="groupCourseSelected($event)"> | ||||
|                         <ion-option *ngFor="let course of courses" [value]="course.id">{{ course.fullname }}</ion-option> | ||||
|                     </ion-select> | ||||
|                 </ion-item> | ||||
|                 <!-- The course has no groups. --> | ||||
|                 <ion-item text-wrap *ngIf="!loadingGroups && courseGroupSet && !groups.length" class="core-danger-item"> | ||||
|                     <p>{{ 'core.coursenogroups' | translate }}</p> | ||||
|                 </ion-item> | ||||
|                 <!-- Select the group. --> | ||||
|                 <ion-item text-wrap *ngIf="!loadingGroups && groups.length > 0"> | ||||
|                     <ion-label id="addon-calendar-group-label"><h2 [core-mark-required]="true">{{ 'core.group' | translate }}</h2></ion-label> | ||||
|                     <ion-select [formControlName]="'groupid'" aria-labelledby="addon-calendar-group-label" interface="action-sheet" [placeholder]="'core.noselection' | translate"> | ||||
|                         <ion-option *ngFor="let group of groups" [value]="group.id">{{ group.name }}</ion-option> | ||||
|                     </ion-select> | ||||
|                 </ion-item> | ||||
|                 <!-- Loading groups. --> | ||||
|                 <ion-item text-wrap *ngIf="loadingGroups"> | ||||
|                     <ion-spinner *ngIf="loadingGroups"></ion-spinner> | ||||
|                 </ion-item> | ||||
|             </ng-container> | ||||
| 
 | ||||
|             <!-- Advanced options. --> | ||||
|             <ion-item-divider text-wrap (click)="toggleAdvanced()" class="core-expandable"> | ||||
|                 <core-icon *ngIf="!advanced" name="fa-caret-right" item-start></core-icon> | ||||
|                 <span *ngIf="!advanced">{{ 'core.showmore' | translate }}</span> | ||||
|                 <core-icon *ngIf="advanced" name="fa-caret-down" item-start></core-icon> | ||||
|                 <span *ngIf="advanced">{{ 'core.showless' | translate }}</span> | ||||
|             </ion-item-divider> | ||||
| 
 | ||||
|             <ng-container *ngIf="advanced"> | ||||
|                 <!-- Description. --> | ||||
|                 <ion-item text-wrap> | ||||
|                     <ion-label stacked><h2>{{ 'core.description' | translate }}</h2></ion-label> | ||||
|                     <core-rich-text-editor item-content [control]="descriptionControl" [placeholder]="'core.description' | translate" name="description" [component]="component" [componentId]="eventId"></core-rich-text-editor> | ||||
|                 </ion-item> | ||||
| 
 | ||||
|                 <!-- Location. --> | ||||
|                 <ion-item text-wrap> | ||||
|                     <ion-label stacked><h2>{{ 'core.location' | translate }}</h2></ion-label> | ||||
|                     <ion-input type="text" name="location" [placeholder]="'core.location' | translate" [formControlName]="'location'"></ion-input> | ||||
|                 </ion-item> | ||||
| 
 | ||||
|                 <!-- Duration. --> | ||||
|                 <div text-wrap radio-group [formControlName]="'duration'" class="addon-calendar-duration-container"> | ||||
|                     <ion-item class="addon-calendar-duration-title"><h2>{{ 'addon.calendar.eventduration' | translate }}</h2></ion-item> | ||||
|                     <ion-item> | ||||
|                         <ion-label>{{ 'addon.calendar.durationnone' | translate }}</ion-label> | ||||
|                         <ion-radio [value]="0"></ion-radio> | ||||
|                     </ion-item> | ||||
|                     <ion-item> | ||||
|                         <ion-label>{{ 'addon.calendar.durationuntil' | translate }}</ion-label> | ||||
|                         <ion-radio [value]="1"></ion-radio> | ||||
|                     </ion-item> | ||||
|                     <ion-item text-wrap> | ||||
|                         <ion-datetime [formControlName]="'timedurationuntil'" [placeholder]="'addon.calendar.durationuntil' | translate" [displayFormat]="dateFormat" [disabled]="eventForm.controls.duration.value != 1"></ion-datetime> | ||||
|                     </ion-item> | ||||
|                     <ion-item> | ||||
|                         <ion-label>{{ 'addon.calendar.durationminutes' | translate }}</ion-label> | ||||
|                         <ion-radio [value]="2"></ion-radio> | ||||
|                     </ion-item> | ||||
|                     <ion-item text-wrap> | ||||
|                         <ion-input type="number" name="timedurationminutes" [placeholder]="'addon.calendar.durationminutes' | translate" [formControlName]="'timedurationminutes'" [disabled]="eventForm.controls.duration.value != 2"></ion-input> | ||||
|                     </ion-item> | ||||
|                 </div> | ||||
| 
 | ||||
|                 <!-- Repeat. --> | ||||
|                 <ion-item text-wrap> | ||||
|                     <ion-label><h2>{{ 'addon.calendar.repeatevent' | translate }}</h2></ion-label> | ||||
|                     <ion-checkbox item-end [formControlName]="'repeat'"></ion-checkbox> | ||||
|                 </ion-item> | ||||
|                 <ion-item text-wrap *ngIf="eventForm.controls.repeat.value"> | ||||
|                     <ion-label stacked><h2>{{ 'addon.calendar.repeatweeksl' | translate }}</h2></ion-label> | ||||
|                     <ion-input type="number" name="repeats" [formControlName]="'repeats'"></ion-input> | ||||
|                 </ion-item> | ||||
|             </ng-container> | ||||
| 
 | ||||
|             <ion-item> | ||||
|                 <ion-row> | ||||
|                     <ion-col> | ||||
|                         <button ion-button block (click)="submit()" [disabled]="!eventForm.valid">{{ 'core.save' | translate }}</button> | ||||
|                     </ion-col> | ||||
|                     <ion-col *ngIf="hasOffline"> | ||||
|                         <button ion-button block color="light" (click)="discard()">{{ 'core.discard' | translate }}</button> | ||||
|                     </ion-col> | ||||
|                 </ion-row> | ||||
|             </ion-item> | ||||
|         </form> | ||||
|     </core-loading> | ||||
| </ion-content> | ||||
							
								
								
									
										33
									
								
								src/addon/calendar/pages/edit-event/edit-event.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/addon/calendar/pages/edit-event/edit-event.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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 { AddonCalendarEditEventPage } from './edit-event'; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     declarations: [ | ||||
|         AddonCalendarEditEventPage, | ||||
|     ], | ||||
|     imports: [ | ||||
|         CoreComponentsModule, | ||||
|         CoreDirectivesModule, | ||||
|         IonicPageModule.forChild(AddonCalendarEditEventPage), | ||||
|         TranslateModule.forChild() | ||||
|     ], | ||||
| }) | ||||
| export class AddonCalendarEditEventPageModule {} | ||||
							
								
								
									
										35
									
								
								src/addon/calendar/pages/edit-event/edit-event.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/addon/calendar/pages/edit-event/edit-event.scss
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | ||||
| ion-app.app-root page-addon-calendar-edit-event { | ||||
|     .addon-calendar-duration-container ion-item:not(.addon-calendar-duration-title) { | ||||
|         &.item-ios { | ||||
|             @include padding-horizontal($item-ios-padding-start * 2, null); | ||||
| 
 | ||||
|             ion-input { | ||||
|                 @include padding-horizontal($datetime-ios-padding-start - $text-input-ios-margin-start, null); | ||||
|             } | ||||
|         } | ||||
|         &.item-md { | ||||
|             @include padding-horizontal($item-md-padding-start * 2, null); | ||||
| 
 | ||||
|             ion-input { | ||||
|                 @include padding-horizontal($datetime-md-padding-start - $text-input-md-margin-start, null); | ||||
|             } | ||||
|         } | ||||
|         &.item-wp { | ||||
|             @include padding-horizontal($item-wp-padding-start * 2, null); | ||||
| 
 | ||||
|             ion-input { | ||||
|                 @include padding-horizontal($datetime-wp-padding-start - $text-input-wp-margin-start, null); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     .addon-calendar-eventtype-container.item-select-disabled { | ||||
|         ion-label, ion-select { | ||||
|             opacity: 1; | ||||
|         } | ||||
| 
 | ||||
|         .select-icon { | ||||
|             display: none; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										392
									
								
								src/addon/calendar/pages/edit-event/edit-event.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										392
									
								
								src/addon/calendar/pages/edit-event/edit-event.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,392 @@ | ||||
| // (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, OnInit, Optional, ViewChild } from '@angular/core'; | ||||
| import { FormControl, FormGroup, FormBuilder, Validators } from '@angular/forms'; | ||||
| import { IonicPage, NavController, NavParams } from 'ionic-angular'; | ||||
| import { TranslateService } from '@ngx-translate/core'; | ||||
| import { CoreEventsProvider } from '@providers/events'; | ||||
| import { CoreGroupsProvider } from '@providers/groups'; | ||||
| import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| import { CoreTimeUtilsProvider } from '@providers/utils/time'; | ||||
| import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||
| import { CoreCoursesProvider } from '@core/courses/providers/courses'; | ||||
| import { CoreSplitViewComponent } from '@components/split-view/split-view'; | ||||
| import { CoreRichTextEditorComponent } from '@components/rich-text-editor/rich-text-editor.ts'; | ||||
| import { AddonCalendarProvider } from '../../providers/calendar'; | ||||
| import { AddonCalendarHelperProvider } from '../../providers/helper'; | ||||
| import { CoreSite } from '@classes/site'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays a form to create/edit an event. | ||||
|  */ | ||||
| @IonicPage({ segment: 'addon-calendar-edit-event' }) | ||||
| @Component({ | ||||
|     selector: 'page-addon-calendar-edit-event', | ||||
|     templateUrl: 'edit-event.html', | ||||
| }) | ||||
| export class AddonCalendarEditEventPage implements OnInit { | ||||
| 
 | ||||
|     @ViewChild(CoreRichTextEditorComponent) descriptionEditor: CoreRichTextEditorComponent; | ||||
| 
 | ||||
|     title: string; | ||||
|     dateFormat: string; | ||||
|     component = AddonCalendarProvider.COMPONENT; | ||||
|     loaded = false; | ||||
|     hasOffline = false; | ||||
|     eventTypes = []; | ||||
|     categories = []; | ||||
|     courses = []; | ||||
|     groups = []; | ||||
|     loadingGroups = false; | ||||
|     courseGroupSet = false; | ||||
|     advanced = false; | ||||
|     errors: any; | ||||
| 
 | ||||
|     // Form variables.
 | ||||
|     eventForm: FormGroup; | ||||
|     eventTypeControl: FormControl; | ||||
|     groupControl: FormControl; | ||||
|     descriptionControl: FormControl; | ||||
| 
 | ||||
|     protected eventId: number; | ||||
|     protected courseId: number; | ||||
|     protected originalData: any; | ||||
|     protected currentSite: CoreSite; | ||||
|     protected types: any; // Object with the supported types.
 | ||||
|     protected showAll: boolean; | ||||
| 
 | ||||
|     constructor(navParams: NavParams, | ||||
|             private navCtrl: NavController, | ||||
|             private translate: TranslateService, | ||||
|             private domUtils: CoreDomUtilsProvider, | ||||
|             private timeUtils: CoreTimeUtilsProvider, | ||||
|             private eventsProvider: CoreEventsProvider, | ||||
|             private groupsProvider: CoreGroupsProvider, | ||||
|             sitesProvider: CoreSitesProvider, | ||||
|             private coursesProvider: CoreCoursesProvider, | ||||
|             private utils: CoreUtilsProvider, | ||||
|             private calendarProvider: AddonCalendarProvider, | ||||
|             private calendarHelper: AddonCalendarHelperProvider, | ||||
|             private fb: FormBuilder, | ||||
|             @Optional() private svComponent: CoreSplitViewComponent) { | ||||
| 
 | ||||
|         this.eventId = navParams.get('eventId'); | ||||
|         this.courseId = navParams.get('courseId'); | ||||
|         this.title = this.eventId ? 'addon.calendar.editevent' : 'addon.calendar.newevent'; | ||||
| 
 | ||||
|         this.currentSite = sitesProvider.getCurrentSite(); | ||||
|         this.errors = { | ||||
|             required: this.translate.instant('core.required') | ||||
|         }; | ||||
| 
 | ||||
|         // Calculate format to use. ion-datetime doesn't support escaping characters ([]), so we remove them.
 | ||||
|         this.dateFormat = this.timeUtils.convertPHPToMoment(this.translate.instant('core.strftimedatetimeshort')) | ||||
|             .replace(/[\[\]]/g, ''); | ||||
| 
 | ||||
|         // Initialize form variables.
 | ||||
|         this.eventForm = new FormGroup({}); | ||||
|         this.eventTypeControl = this.fb.control('', Validators.required); | ||||
|         this.groupControl = this.fb.control(''); | ||||
|         this.descriptionControl = this.fb.control(''); | ||||
| 
 | ||||
|         this.eventForm.addControl('name', this.fb.control('', Validators.required)); | ||||
|         this.eventForm.addControl('timestart', this.fb.control(new Date().toISOString(), Validators.required)); | ||||
|         this.eventForm.addControl('eventtype', this.eventTypeControl); | ||||
|         this.eventForm.addControl('categoryid', this.fb.control('')); | ||||
|         this.eventForm.addControl('courseid', this.fb.control(this.courseId)); | ||||
|         this.eventForm.addControl('groupcourseid', this.fb.control('')); | ||||
|         this.eventForm.addControl('groupid', this.groupControl); | ||||
|         this.eventForm.addControl('description', this.descriptionControl); | ||||
|         this.eventForm.addControl('location', this.fb.control('')); | ||||
|         this.eventForm.addControl('duration', this.fb.control(0)); | ||||
|         this.eventForm.addControl('timedurationuntil', this.fb.control(new Date().toISOString())); | ||||
|         this.eventForm.addControl('timedurationminutes', this.fb.control('')); | ||||
|         this.eventForm.addControl('repeat', this.fb.control(false)); | ||||
|         this.eventForm.addControl('repeats', this.fb.control('1')); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Component being initialized. | ||||
|      */ | ||||
|     ngOnInit(): void { | ||||
|         this.fetchData().finally(() => { | ||||
|             this.originalData = this.utils.clone(this.eventForm.value); | ||||
|             this.loaded = true; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Fetch the data needed to render the form. | ||||
|      * | ||||
|      * @return {Promise<any>} Promise resolved when done. | ||||
|      */ | ||||
|     protected fetchData(): Promise<any> { | ||||
|         let accessInfo; | ||||
| 
 | ||||
|         // Get access info.
 | ||||
|         return this.calendarProvider.getAccessInformation().then((info) => { | ||||
|             accessInfo = info; | ||||
| 
 | ||||
|             return this.calendarProvider.getAllowedEventTypes(); | ||||
|         }).then((types) => { | ||||
|             this.types = types; | ||||
| 
 | ||||
|             const promises = [], | ||||
|                 eventTypes = this.calendarHelper.getEventTypeOptions(types); | ||||
| 
 | ||||
|             if (!eventTypes.length) { | ||||
|                 return Promise.reject(this.translate.instant('addon.calendar.nopermissiontoupdatecalendar')); | ||||
|             } | ||||
| 
 | ||||
|             if (types.category) { | ||||
|                 // Get the categories.
 | ||||
|                 promises.push(this.coursesProvider.getCategories(0, true).then((cats) => { | ||||
|                     this.categories = cats; | ||||
|                 })); | ||||
|             } | ||||
| 
 | ||||
|             this.showAll = this.utils.isTrueOrOne(this.currentSite.getStoredConfig('calendar_adminseesall')) && | ||||
|                     accessInfo.canmanageentries; | ||||
| 
 | ||||
|             if (types.course || types.groups) { | ||||
|                 // Get the courses.
 | ||||
|                 const promise = this.showAll ? this.coursesProvider.getCoursesByField() : this.coursesProvider.getUserCourses(); | ||||
| 
 | ||||
|                 promises.push(promise.then((courses) => { | ||||
|                     if (this.showAll) { | ||||
|                         // Remove site home from the list of courses.
 | ||||
|                         const siteHomeId = this.currentSite.getSiteHomeId(); | ||||
|                         courses = courses.filter((course) => { | ||||
|                             return course.id != siteHomeId; | ||||
|                         }); | ||||
|                     } | ||||
| 
 | ||||
|                     // Sort courses by name.
 | ||||
|                     this.courses = courses.sort((a, b) => { | ||||
|                         const compareA = a.fullname.toLowerCase(), | ||||
|                             compareB = b.fullname.toLowerCase(); | ||||
| 
 | ||||
|                         return compareA.localeCompare(compareB); | ||||
|                     }); | ||||
|                 })); | ||||
|             } | ||||
| 
 | ||||
|             return Promise.all(promises).then(() => { | ||||
|                 // Set event types. If course is allowed, select it first.
 | ||||
|                 if (types.course) { | ||||
|                     this.eventTypeControl.setValue(AddonCalendarProvider.TYPE_COURSE); | ||||
|                 } else { | ||||
|                     this.eventTypeControl.setValue(eventTypes[0].value); | ||||
|                 } | ||||
| 
 | ||||
|                 this.eventTypes = eventTypes; | ||||
|             }); | ||||
| 
 | ||||
|         }).catch((error) => { | ||||
|             this.domUtils.showErrorModalDefault(error, 'Error getting data.'); | ||||
|             this.originalData = null; // Avoid asking for confirmation.
 | ||||
|             this.navCtrl.pop(); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Pull to refresh. | ||||
|      * | ||||
|      * @param {any} refresher Refresher. | ||||
|      */ | ||||
|     refreshData(refresher: any): void { | ||||
|         const promises = [ | ||||
|             this.calendarProvider.invalidateAccessInformation(this.courseId), | ||||
|             this.calendarProvider.invalidateAllowedEventTypes(this.courseId) | ||||
|         ]; | ||||
| 
 | ||||
|         if (this.types) { | ||||
|             if (this.types.category) { | ||||
|                 promises.push(this.coursesProvider.invalidateCategories(0, true)); | ||||
|             } | ||||
|             if (this.types.course || this.types.groups) { | ||||
|                 if (this.showAll) { | ||||
|                     promises.push(this.coursesProvider.invalidateCoursesByField()); | ||||
|                 } else { | ||||
|                     promises.push(this.coursesProvider.invalidateUserCourses()); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         Promise.all(promises).finally(() => { | ||||
|             this.fetchData().finally(() => { | ||||
|                 refresher.complete(); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * A course was selected, get its groups. | ||||
|      * | ||||
|      * @param {number} courseId Course ID. | ||||
|      */ | ||||
|     groupCourseSelected(courseId: number): void { | ||||
|         if (!courseId) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         const modal = this.domUtils.showModalLoading(); | ||||
|         this.loadingGroups = true; | ||||
| 
 | ||||
|         this.groupsProvider.getUserGroupsInCourse(courseId).then((groups) => { | ||||
|             this.groups = groups; | ||||
|             this.courseGroupSet = true; | ||||
|             this.groupControl.setValue(''); | ||||
|         }).catch((error) => { | ||||
|             this.domUtils.showErrorModalDefault(error, 'Error getting data.'); | ||||
|         }).finally(() => { | ||||
|             this.loadingGroups = false; | ||||
|             modal.dismiss(); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Show or hide advanced form fields. | ||||
|      */ | ||||
|     toggleAdvanced(): void { | ||||
|         this.advanced = !this.advanced; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Create the event. | ||||
|      */ | ||||
|     submit(): void { | ||||
|         // Validate data.
 | ||||
|         const formData = this.eventForm.value, | ||||
|             timeStartDate = new Date(formData.timestart), | ||||
|             timeUntilDate = new Date(formData.timedurationuntil), | ||||
|             timeDurationMinutes = parseInt(formData.timedurationminutes || '', 10); | ||||
|         let error; | ||||
| 
 | ||||
|         if (formData.eventtype == AddonCalendarProvider.TYPE_COURSE && !formData.courseid) { | ||||
|             error = 'core.selectacourse'; | ||||
|         } else if (formData.eventtype == AddonCalendarProvider.TYPE_GROUP && !formData.groupcourseid) { | ||||
|             error = 'core.selectacourse'; | ||||
|         } else if (formData.eventtype == AddonCalendarProvider.TYPE_GROUP && !formData.groupid) { | ||||
|             error = 'core.selectagroup'; | ||||
|         } else if (formData.eventtype == AddonCalendarProvider.TYPE_CATEGORY && !formData.categoryid) { | ||||
|             error = 'core.selectacategory'; | ||||
|         } else if (formData.duration == 1 && timeStartDate.getTime() > timeUntilDate.getTime()) { | ||||
|             error = 'addon.calendar.invalidtimedurationuntil'; | ||||
|         } else if (formData.duration == 2 && (isNaN(timeDurationMinutes) || timeDurationMinutes < 1)) { | ||||
|             error = 'addon.calendar.invalidtimedurationminutes'; | ||||
|         } | ||||
| 
 | ||||
|         if (error) { | ||||
|             // Show error and stop.
 | ||||
|             this.domUtils.showErrorModal(this.translate.instant(error)); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // Format the data to send.
 | ||||
|         const data: any = { | ||||
|             name: formData.name, | ||||
|             eventtype: formData.eventtype, | ||||
|             timestart: Math.floor(timeStartDate.getTime() / 1000), | ||||
|             description: { | ||||
|                 text: formData.description, | ||||
|                 format: 1 | ||||
|             }, | ||||
|             location: formData.location, | ||||
|             duration: formData.duration, | ||||
|             repeat: formData.repeat | ||||
|         }; | ||||
| 
 | ||||
|         if (formData.eventtype == AddonCalendarProvider.TYPE_COURSE) { | ||||
|             data.courseid = formData.courseid; | ||||
|         } else if (formData.eventtype == AddonCalendarProvider.TYPE_GROUP) { | ||||
|             data.groupcourseid = formData.groupcourseid; | ||||
|             data.groupid = formData.groupid; | ||||
|         } else if (formData.eventtype == AddonCalendarProvider.TYPE_CATEGORY) { | ||||
|             data.categoryid = formData.categoryid; | ||||
|         } | ||||
| 
 | ||||
|         if (formData.duration == 1) { | ||||
|             data.timedurationuntil = Math.floor(timeUntilDate.getTime() / 1000); | ||||
|         } else if (formData.duration == 2) { | ||||
|             data.timedurationminutes = formData.timedurationminutes; | ||||
|         } | ||||
| 
 | ||||
|         if (formData.repeat) { | ||||
|             data.repeats = formData.repeats; | ||||
|         } | ||||
| 
 | ||||
|         // Send the data.
 | ||||
|         const modal = this.domUtils.showModalLoading('core.sending'); | ||||
| 
 | ||||
|         this.calendarProvider.submitEvent(this.eventId, data).then((event) => { | ||||
|             this.returnToList(event); | ||||
|         }).catch((error) => { | ||||
|             this.domUtils.showErrorModalDefault(error, 'Error sending data.'); | ||||
|         }).finally(() => { | ||||
|             modal.dismiss(); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Convenience function to update or return to event list depending on device. | ||||
|      * | ||||
|      * @param {number} [event] Event. | ||||
|      */ | ||||
|     protected returnToList(event?: any): void { | ||||
|         const data: any = { | ||||
|             event: event | ||||
|         }; | ||||
|         this.eventsProvider.trigger(AddonCalendarProvider.NEW_EVENT_EVENT, data, this.currentSite.getId()); | ||||
| 
 | ||||
|         if (this.svComponent && this.svComponent.isOn()) { | ||||
|             // Empty form.
 | ||||
|             this.hasOffline = false; | ||||
|             this.eventForm.reset(this.originalData); | ||||
|             this.originalData = this.utils.clone(this.eventForm.value); | ||||
|         } else { | ||||
|             this.originalData = null; // Avoid asking for confirmation.
 | ||||
|             this.navCtrl.pop(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Discard an offline saved discussion. | ||||
|      */ | ||||
|     discard(): void { | ||||
|         this.domUtils.showConfirm(this.translate.instant('core.areyousure')).then(() => { | ||||
|             // @todo.
 | ||||
|         }).catch(() => { | ||||
|             // Cancelled.
 | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if we can leave the page or not. | ||||
|      * | ||||
|      * @return {boolean|Promise<void>} Resolved if we can leave it, rejected if not. | ||||
|      */ | ||||
|     ionViewCanLeave(): boolean | Promise<void> { | ||||
| 
 | ||||
|         if (this.calendarHelper.hasEventDataChanged(this.eventForm.value, this.originalData)) { | ||||
|             // Show confirmation if some data has been modified.
 | ||||
|             return this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit')); | ||||
|         } else { | ||||
|             return Promise.resolve(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -40,5 +40,12 @@ | ||||
| 
 | ||||
|             <core-infinite-loading [enabled]="canLoadMore" (action)="loadMoreEvents($event)" [error]="loadMoreError"></core-infinite-loading> | ||||
|         </core-loading> | ||||
| 
 | ||||
|         <!-- Create a calendar event. --> | ||||
|         <ion-fab core-fab bottom end *ngIf="canCreate"> | ||||
|             <button ion-fab (click)="openCreate()" [attr.aria-label]="'addon.calendar.newevent' | translate"> | ||||
|                 <ion-icon name="add"></ion-icon> | ||||
|             </button> | ||||
|         </ion-fab> | ||||
|     </ion-content> | ||||
| </core-split-view> | ||||
| @ -54,6 +54,7 @@ export class AddonCalendarListPage implements OnDestroy { | ||||
|     protected obsDefaultTimeChange: any; | ||||
|     protected eventId: number; | ||||
|     protected preSelectedCourseId: number; | ||||
|     protected newEventObserver: any; | ||||
| 
 | ||||
|     courses: any[]; | ||||
|     eventsLoaded = false; | ||||
| @ -65,6 +66,7 @@ export class AddonCalendarListPage implements OnDestroy { | ||||
|     filter = { | ||||
|         course: this.allCourses | ||||
|     }; | ||||
|     canCreate = false; | ||||
| 
 | ||||
|     constructor(private translate: TranslateService, private calendarProvider: AddonCalendarProvider, navParams: NavParams, | ||||
|             private domUtils: CoreDomUtilsProvider, private coursesProvider: CoreCoursesProvider, private utils: CoreUtilsProvider, | ||||
| @ -74,6 +76,7 @@ export class AddonCalendarListPage implements OnDestroy { | ||||
| 
 | ||||
|         this.siteHomeId = sitesProvider.getCurrentSite().getSiteHomeId(); | ||||
|         this.notificationsEnabled = localNotificationsProvider.isAvailable(); | ||||
| 
 | ||||
|         if (this.notificationsEnabled) { | ||||
|             // Re-schedule events if default time changes.
 | ||||
|             this.obsDefaultTimeChange = eventsProvider.on(AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_CHANGED, () => { | ||||
| @ -83,6 +86,30 @@ export class AddonCalendarListPage implements OnDestroy { | ||||
| 
 | ||||
|         this.eventId = navParams.get('eventId') || false; | ||||
|         this.preSelectedCourseId = navParams.get('courseId') || null; | ||||
| 
 | ||||
|         // Listen for events added. When an event is added, we reload the data.
 | ||||
|         this.newEventObserver = eventsProvider.on(AddonCalendarProvider.NEW_EVENT_EVENT, (data) => { | ||||
|             if (data && data.event) { | ||||
|                 if (this.splitviewCtrl.isOn()) { | ||||
|                     // Discussion added, clear details page.
 | ||||
|                     this.splitviewCtrl.emptyDetails(); | ||||
|                 } | ||||
| 
 | ||||
|                 this.eventsLoaded = false; | ||||
|                 this.refreshEvents(false).finally(() => { | ||||
|                     this.eventsLoaded = true; | ||||
| 
 | ||||
|                     // In tablet mode try to open the event.
 | ||||
|                     if (this.splitviewCtrl.isOn()) { | ||||
|                         if (data.event.id) { | ||||
|                             this.gotoEvent(data.event.id); | ||||
|                         } else { | ||||
|                             // It's an offline event.
 | ||||
|                         } | ||||
|                     } | ||||
|                 }); | ||||
|             } | ||||
|         }, sitesProvider.getCurrentSiteId()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -114,8 +141,19 @@ export class AddonCalendarListPage implements OnDestroy { | ||||
|         this.daysLoaded = 0; | ||||
|         this.emptyEventsTimes = 0; | ||||
| 
 | ||||
|         const promises = []; | ||||
| 
 | ||||
|         if (this.calendarProvider.canEditEventsInSite()) { | ||||
|             // Site allows creating events. Check if the user has permissions to do so.
 | ||||
|             promises.push(this.calendarProvider.getAllowedEventTypes().then((types) => { | ||||
|                 this.canCreate = Object.keys(types).length > 0; | ||||
|             }).catch(() => { | ||||
|                 this.canCreate = false; | ||||
|             })); | ||||
|         } | ||||
| 
 | ||||
|         // Load courses for the popover.
 | ||||
|         return this.coursesProvider.getUserCourses(false).then((courses) => { | ||||
|         promises.push(this.coursesProvider.getUserCourses(false).then((courses) => { | ||||
|             // Add "All courses".
 | ||||
|             courses.unshift(this.allCourses); | ||||
|             this.courses = courses; | ||||
| @ -127,7 +165,9 @@ export class AddonCalendarListPage implements OnDestroy { | ||||
|             } | ||||
| 
 | ||||
|             return this.fetchEvents(refresh); | ||||
|         }); | ||||
|         })); | ||||
| 
 | ||||
|         return Promise.all(promises); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -308,21 +348,23 @@ export class AddonCalendarListPage implements OnDestroy { | ||||
|     /** | ||||
|      * Refresh the events. | ||||
|      * | ||||
|      * @param {any} refresher Refresher. | ||||
|      * @param {any} [refresher] Refresher. | ||||
|      * @return {Promise<any>} Promise resolved when done. | ||||
|      */ | ||||
|     refreshEvents(refresher: any): void { | ||||
|     refreshEvents(refresher?: any): Promise<any> { | ||||
|         const promises = []; | ||||
| 
 | ||||
|         promises.push(this.calendarProvider.invalidateEventsList()); | ||||
|         promises.push(this.calendarProvider.invalidateAllowedEventTypes()); | ||||
| 
 | ||||
|         if (this.categoriesRetrieved) { | ||||
|             promises.push(this.coursesProvider.invalidateCategories(0, true)); | ||||
|             this.categoriesRetrieved = false; | ||||
|         } | ||||
| 
 | ||||
|         Promise.all(promises).finally(() => { | ||||
|             this.fetchData(true).finally(() => { | ||||
|                 refresher.complete(); | ||||
|         return Promise.all(promises).finally(() => { | ||||
|             return this.fetchData(true).finally(() => { | ||||
|                 refresher && refresher.complete(); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| @ -384,6 +426,18 @@ export class AddonCalendarListPage implements OnDestroy { | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Open page to create an event. | ||||
|      */ | ||||
|     openCreate(): void { | ||||
|         const params: any = {}; | ||||
|         if (this.filter.course.id != this.allCourses.id) { | ||||
|             params.courseId = this.filter.course.id; | ||||
|         } | ||||
| 
 | ||||
|         this.splitviewCtrl.push('AddonCalendarEditEventPage', params); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Open calendar events settings. | ||||
|      */ | ||||
| @ -406,5 +460,6 @@ export class AddonCalendarListPage implements OnDestroy { | ||||
|      */ | ||||
|     ngOnDestroy(): void { | ||||
|         this.obsDefaultTimeChange && this.obsDefaultTimeChange.off(); | ||||
|         this.newEventObserver && this.newEventObserver.off(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -18,6 +18,7 @@ import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; | ||||
| import { CoreSite } from '@classes/site'; | ||||
| import { CoreCoursesProvider } from '@core/courses/providers/courses'; | ||||
| import { CoreTimeUtilsProvider } from '@providers/utils/time'; | ||||
| import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||
| import { CoreGroupsProvider } from '@providers/groups'; | ||||
| import { CoreConstants } from '@core/constants'; | ||||
| import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; | ||||
| @ -35,6 +36,12 @@ export class AddonCalendarProvider { | ||||
|     static DEFAULT_NOTIFICATION_TIME_CHANGED = 'AddonCalendarDefaultNotificationTimeChangedEvent'; | ||||
|     static DEFAULT_NOTIFICATION_TIME_SETTING = 'mmaCalendarDefaultNotifTime'; | ||||
|     static DEFAULT_NOTIFICATION_TIME = 60; | ||||
|     static NEW_EVENT_EVENT = 'addon_calendar_new_event'; | ||||
|     static TYPE_CATEGORY = 'category'; | ||||
|     static TYPE_COURSE = 'course'; | ||||
|     static TYPE_GROUP = 'group'; | ||||
|     static TYPE_SITE = 'site'; | ||||
|     static TYPE_USER = 'user'; | ||||
|     protected ROOT_CACHE_KEY = 'mmaCalendar:'; | ||||
| 
 | ||||
|     // Variables for database.
 | ||||
| @ -206,11 +213,37 @@ export class AddonCalendarProvider { | ||||
| 
 | ||||
|     constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private groupsProvider: CoreGroupsProvider, | ||||
|             private coursesProvider: CoreCoursesProvider, private timeUtils: CoreTimeUtilsProvider, | ||||
|             private localNotificationsProvider: CoreLocalNotificationsProvider, private configProvider: CoreConfigProvider) { | ||||
|             private localNotificationsProvider: CoreLocalNotificationsProvider, private configProvider: CoreConfigProvider, | ||||
|             private utils: CoreUtilsProvider) { | ||||
|         this.logger = logger.getInstance('AddonCalendarProvider'); | ||||
|         this.sitesProvider.registerSiteSchema(this.siteSchema); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if a certain site allows creating and editing events. | ||||
|      * | ||||
|      * @param {string} [siteId] Site Id. If not defined, use current site. | ||||
|      * @return {Promise<boolean>} Promise resolved with true if can create/edit. | ||||
|      */ | ||||
|     canEditEvents(siteId?: string): Promise<boolean> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             return this.canEditEventsInSite(site); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if a certain site allows creating and editing events. | ||||
|      * | ||||
|      * @param {CoreSite} [site] Site. If not defined, use current site. | ||||
|      * @return {boolean} Whether events can be created and edited. | ||||
|      */ | ||||
|     canEditEventsInSite(site?: CoreSite): boolean { | ||||
|         site = site || this.sitesProvider.getCurrentSite(); | ||||
| 
 | ||||
|         // The WS to create/edit events requires a fix that was integrated in 3.7.1.
 | ||||
|         return site.isVersionGreaterEqualThan('3.7.1'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Removes expired events from local DB. | ||||
|      * | ||||
| @ -255,6 +288,39 @@ export class AddonCalendarProvider { | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get access information for a calendar (either course calendar or site calendar). | ||||
|      * | ||||
|      * @param {number} [courseId] Course ID. If not defined, site calendar. | ||||
|      * @param {string} [siteId] Site ID. If not defined, current site. | ||||
|      * @return {Promise<any>} Promise resolved with object with access information. | ||||
|      * @since 3.7 | ||||
|      */ | ||||
|     getAccessInformation(courseId?: number, siteId?: string): Promise<any> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             const params: any = {}, | ||||
|                 preSets = { | ||||
|                     cacheKey: this.getAccessInformationCacheKey(courseId) | ||||
|                 }; | ||||
| 
 | ||||
|             if (courseId) { | ||||
|                 params.courseid = courseId; | ||||
|             } | ||||
| 
 | ||||
|             return site.read('core_calendar_get_calendar_access_information', params, preSets); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get cache key for calendar access information WS calls. | ||||
|      * | ||||
|      * @param {number} [courseId] Course ID. | ||||
|      * @return {string} Cache key. | ||||
|      */ | ||||
|     protected getAccessInformationCacheKey(courseId?: number): string { | ||||
|         return this.ROOT_CACHE_KEY + 'accessInformation:' + (courseId || 0); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get all calendar events from local Db. | ||||
|      * | ||||
| @ -267,6 +333,50 @@ export class AddonCalendarProvider { | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the type of events a user can create (either course calendar or site calendar). | ||||
|      * | ||||
|      * @param {number} [courseId] Course ID. If not defined, site calendar. | ||||
|      * @param {string} [siteId] Site ID. If not defined, current site. | ||||
|      * @return {Promise<any>} Promise resolved with an object indicating the types. | ||||
|      * @since 3.7 | ||||
|      */ | ||||
|     getAllowedEventTypes(courseId?: number, siteId?: string): Promise<any> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             const params: any = {}, | ||||
|                 preSets = { | ||||
|                     cacheKey: this.getAllowedEventTypesCacheKey(courseId) | ||||
|                 }; | ||||
| 
 | ||||
|             if (courseId) { | ||||
|                 params.courseid = courseId; | ||||
|             } | ||||
| 
 | ||||
|             return site.read('core_calendar_get_allowed_event_types', params, preSets).then((response) => { | ||||
|                 // Convert the array to an object.
 | ||||
|                 const result = {}; | ||||
| 
 | ||||
|                 if (response.allowedeventtypes) { | ||||
|                     response.allowedeventtypes.map((type) => { | ||||
|                         result[type] = true; | ||||
|                     }); | ||||
|                 } | ||||
| 
 | ||||
|                 return result; | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get cache key for calendar allowed event types WS calls. | ||||
|      * | ||||
|      * @param {number} [courseId] Course ID. | ||||
|      * @return {string} Cache key. | ||||
|      */ | ||||
|     protected getAllowedEventTypesCacheKey(courseId?: number): string { | ||||
|         return this.ROOT_CACHE_KEY + 'allowedEventTypes:' + (courseId || 0); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the configured default notification time. | ||||
|      * | ||||
| @ -504,6 +614,32 @@ export class AddonCalendarProvider { | ||||
|         return this.getEventsListPrefixCacheKey() + daysToStart + ':' + daysInterval; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Invalidates access information. | ||||
|      * | ||||
|      * @param {number} [courseId] Course ID. If not defined, site calendar. | ||||
|      * @param {string} [siteId] Site ID. If not defined, current site. | ||||
|      * @return {Promise<any>}  Promise resolved when the data is invalidated. | ||||
|      */ | ||||
|     invalidateAccessInformation(courseId?: number, siteId?: string): Promise<any> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             return site.invalidateWsCacheForKey(this.getAccessInformationCacheKey(courseId)); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Invalidates allowed event types. | ||||
|      * | ||||
|      * @param {number} [courseId] Course ID. If not defined, site calendar. | ||||
|      * @param {string} [siteId] Site ID. If not defined, current site. | ||||
|      * @return {Promise<any>}  Promise resolved when the data is invalidated. | ||||
|      */ | ||||
|     invalidateAllowedEventTypes(courseId?: number, siteId?: string): Promise<any> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             return site.invalidateWsCacheForKey(this.getAllowedEventTypesCacheKey(courseId)); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Invalidates events list and all the single events and related info. | ||||
|      * | ||||
| @ -780,4 +916,35 @@ export class AddonCalendarProvider { | ||||
|             })); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Submit an event, either to create it or to edit it. | ||||
|      * | ||||
|      * @param {number} eventId ID of the event. If undefined/null, create a new event. | ||||
|      * @param {any} formData Form data. | ||||
|      * @param {string} [siteId] Site ID. If not provided, current site. | ||||
|      * @return {Promise<any>} Promise resolved when done. | ||||
|      */ | ||||
|     submitEvent(eventId: number, formData: any, siteId?: string): Promise<any> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             // Add data that is "hidden" in web.
 | ||||
|             formData.id = eventId || 0; | ||||
|             formData.userid = site.getUserId(); | ||||
|             formData.visible = 1; | ||||
|             formData.instance = 0; | ||||
|             formData['_qf__core_calendar_local_event_forms_create'] = 1; | ||||
| 
 | ||||
|             const params = { | ||||
|                 formdata: this.utils.objectToGetParams(formData) | ||||
|             }; | ||||
| 
 | ||||
|             return site.write('core_calendar_submit_create_update_form', params).then((result) => { | ||||
|                 if (result.validationerror) { | ||||
|                     return Promise.reject(null); | ||||
|                 } | ||||
| 
 | ||||
|                 return result.event; | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -15,6 +15,7 @@ | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { CoreLoggerProvider } from '@providers/logger'; | ||||
| import { CoreCourseProvider } from '@core/course/providers/course'; | ||||
| import { AddonCalendarProvider } from './calendar'; | ||||
| 
 | ||||
| /** | ||||
|  * Service that provides some features regarding lists of courses and categories. | ||||
| @ -47,4 +48,73 @@ export class AddonCalendarHelperProvider { | ||||
|             e.moduleIcon = e.icon; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get options (name & value) for each allowed event type. | ||||
|      * | ||||
|      * @param {any} eventTypes Result of getAllowedEventTypes. | ||||
|      * @return {{name: string, value: string}[]} Options. | ||||
|      */ | ||||
|     getEventTypeOptions(eventTypes: any): {name: string, value: string}[] { | ||||
|         const options = []; | ||||
| 
 | ||||
|         if (eventTypes.user) { | ||||
|             options.push({name: 'core.user', value: AddonCalendarProvider.TYPE_USER}); | ||||
|         } | ||||
|         if (eventTypes.group) { | ||||
|             options.push({name: 'core.group', value: AddonCalendarProvider.TYPE_GROUP}); | ||||
|         } | ||||
|         if (eventTypes.course) { | ||||
|             options.push({name: 'core.course', value: AddonCalendarProvider.TYPE_COURSE}); | ||||
|         } | ||||
|         if (eventTypes.category) { | ||||
|             options.push({name: 'core.category', value: AddonCalendarProvider.TYPE_CATEGORY}); | ||||
|         } | ||||
|         if (eventTypes.site) { | ||||
|             options.push({name: 'core.site', value: AddonCalendarProvider.TYPE_SITE}); | ||||
|         } | ||||
| 
 | ||||
|         return options; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if the data of an event has changed. | ||||
|      * | ||||
|      * @param {any} data Current data. | ||||
|      * @param {any} [original] Original data. | ||||
|      * @return {boolean} True if data has changed, false otherwise. | ||||
|      */ | ||||
|     hasEventDataChanged(data: any, original?: any): boolean { | ||||
|         if (!original) { | ||||
|             // There is no original data, assume it hasn't changed.
 | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         // Check the fields that don't depend on any other.
 | ||||
|         if (data.name != original.name || data.timestart != original.timestart || data.eventtype != original.eventtype || | ||||
|                 data.description != original.description || data.location != original.location || | ||||
|                 data.duration != original.duration || data.repeat != original.repeat) { | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         // Check data that depends on eventtype.
 | ||||
|         if ((data.eventtype == AddonCalendarProvider.TYPE_CATEGORY && data.categoryid != original.categoryid) || | ||||
|                 (data.eventtype == AddonCalendarProvider.TYPE_COURSE && data.courseid != original.courseid) || | ||||
|                 (data.eventtype == AddonCalendarProvider.TYPE_GROUP && data.groupcourseid != original.groupcourseid && | ||||
|                     data.groupid != original.groupid)) { | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         // Check data that depends on duration.
 | ||||
|         if ((data.duration == 1 && data.timedurationuntil != original.timedurationuntil) || | ||||
|                 (data.duration == 2 && data.timedurationminutes != original.timedurationminutes)) { | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         if (data.repeat && data.repeats != original.repeats) { | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -87,13 +87,26 @@ | ||||
|     "addon.calendar.calendarevents": "Calendar events", | ||||
|     "addon.calendar.calendarreminders": "Calendar reminders", | ||||
|     "addon.calendar.defaultnotificationtime": "Default notification time", | ||||
|     "addon.calendar.durationminutes": "Duration in minutes", | ||||
|     "addon.calendar.durationnone": "Without duration", | ||||
|     "addon.calendar.durationuntil": "Until", | ||||
|     "addon.calendar.editevent": "Editing event", | ||||
|     "addon.calendar.errorloadevent": "Error loading event.", | ||||
|     "addon.calendar.errorloadevents": "Error loading events.", | ||||
|     "addon.calendar.eventduration": "Duration", | ||||
|     "addon.calendar.eventendtime": "End time", | ||||
|     "addon.calendar.eventname": "Event title", | ||||
|     "addon.calendar.eventstarttime": "Start time", | ||||
|     "addon.calendar.eventtype": "Event type", | ||||
|     "addon.calendar.gotoactivity": "Go to activity", | ||||
|     "addon.calendar.invalidtimedurationminutes": "The duration in minutes you have entered is invalid. Please enter the duration in minutes greater than 0 or select no duration.", | ||||
|     "addon.calendar.invalidtimedurationuntil": "The date and time you selected for duration until is before the start time of the event. Please correct this before proceeding.", | ||||
|     "addon.calendar.newevent": "New event", | ||||
|     "addon.calendar.noevents": "There are no events", | ||||
|     "addon.calendar.nopermissiontoupdatecalendar": "Sorry, but you do not have permission to update the calendar event", | ||||
|     "addon.calendar.reminders": "Reminders", | ||||
|     "addon.calendar.repeatevent": "Repeat this event", | ||||
|     "addon.calendar.repeatweeksl": "Repeat weekly, creating altogether", | ||||
|     "addon.calendar.setnewreminder": "Set a new reminder", | ||||
|     "addon.calendar.typecategory": "Category event", | ||||
|     "addon.calendar.typeclose": "Close event", | ||||
| @ -1328,6 +1341,7 @@ | ||||
|     "core.course.warningmanualcompletionmodified": "The manual completion of an activity was modified on the site.", | ||||
|     "core.course.warningofflinemanualcompletiondeleted": "Some offline manual completion of course '{{name}}' has been deleted. {{error}}", | ||||
|     "core.coursedetails": "Course details", | ||||
|     "core.coursenogroups": "This course doesn't have any group.", | ||||
|     "core.courses.addtofavourites": "Star this course", | ||||
|     "core.courses.allowguests": "This course allows guest users to enter", | ||||
|     "core.courses.availablecourses": "Available courses", | ||||
| @ -1457,6 +1471,7 @@ | ||||
|     "core.grades.range": "Range", | ||||
|     "core.grades.rank": "Rank", | ||||
|     "core.grades.weight": "Weight", | ||||
|     "core.group": "Group", | ||||
|     "core.groupsseparate": "Separate groups", | ||||
|     "core.groupsvisible": "Visible groups", | ||||
|     "core.hasdatatosync": "This {{$a}} has offline data to be synchronised.", | ||||
| @ -1624,6 +1639,7 @@ | ||||
|     "core.nopermissionerror": "Sorry, but you do not currently have permissions to do that", | ||||
|     "core.nopermissions": "Sorry, but you do not currently have permissions to do that ({{$a}}).", | ||||
|     "core.noresults": "No results", | ||||
|     "core.noselection": "No selection", | ||||
|     "core.notapplicable": "n/a", | ||||
|     "core.notenrolledprofile": "This profile is not available because this user is not enrolled in this course.", | ||||
|     "core.notice": "Notice", | ||||
| @ -1692,6 +1708,9 @@ | ||||
|     "core.sec": "sec", | ||||
|     "core.secs": "secs", | ||||
|     "core.seemoredetail": "Click here to see more detail", | ||||
|     "core.selectacategory": "Please select a category", | ||||
|     "core.selectacourse": "Select a course", | ||||
|     "core.selectagroup": "Select a group", | ||||
|     "core.send": "Send", | ||||
|     "core.sending": "Sending", | ||||
|     "core.serverconnection": "Error connecting to the server", | ||||
| @ -1760,6 +1779,7 @@ | ||||
|     "core.sharedfiles.sharedfiles": "Shared files", | ||||
|     "core.sharedfiles.successstorefile": "File successfully stored. Select the file to upload to your private files or use in an activity.", | ||||
|     "core.show": "Show", | ||||
|     "core.showless": "Show less...", | ||||
|     "core.showmore": "Show more...", | ||||
|     "core.site": "Site", | ||||
|     "core.sitehome.sitehome": "Site home", | ||||
| @ -1808,6 +1828,7 @@ | ||||
|     "core.unlimited": "Unlimited", | ||||
|     "core.unzipping": "Unzipping", | ||||
|     "core.upgraderunning": "Site is being upgraded, please retry later.", | ||||
|     "core.user": "User", | ||||
|     "core.user.address": "Address", | ||||
|     "core.user.city": "City/town", | ||||
|     "core.user.contact": "Contact", | ||||
|  | ||||
| @ -51,6 +51,7 @@ | ||||
|     "copiedtoclipboard": "Text copied to clipboard", | ||||
|     "course": "Course", | ||||
|     "coursedetails": "Course details", | ||||
|     "coursenogroups": "This course doesn't have any group.", | ||||
|     "currentdevice": "Current device", | ||||
|     "datastoredoffline": "Data stored in the device because it couldn't be sent. It will be sent automatically later.", | ||||
|     "date": "Date", | ||||
| @ -104,6 +105,7 @@ | ||||
|     "forcepasswordchangenotice": "You must change your password to proceed.", | ||||
|     "fulllistofcourses": "All courses", | ||||
|     "fullnameandsitename": "{{fullname}} ({{sitename}})", | ||||
|     "group": "Group", | ||||
|     "groupsseparate": "Separate groups", | ||||
|     "groupsvisible": "Visible groups", | ||||
|     "hasdatatosync": "This {{$a}} has offline data to be synchronised.", | ||||
| @ -174,6 +176,7 @@ | ||||
|     "nopermissionerror": "Sorry, but you do not currently have permissions to do that", | ||||
|     "nopermissions": "Sorry, but you do not currently have permissions to do that ({{$a}}).", | ||||
|     "noresults": "No results", | ||||
|     "noselection": "No selection", | ||||
|     "notapplicable": "n/a", | ||||
|     "notenrolledprofile": "This profile is not available because this user is not enrolled in this course.", | ||||
|     "notice": "Notice", | ||||
| @ -214,10 +217,14 @@ | ||||
|     "sec": "sec", | ||||
|     "secs": "secs", | ||||
|     "seemoredetail": "Click here to see more detail", | ||||
|     "selectacategory": "Please select a category", | ||||
|     "selectacourse": "Select a course", | ||||
|     "selectagroup": "Select a group", | ||||
|     "send": "Send", | ||||
|     "sending": "Sending", | ||||
|     "serverconnection": "Error connecting to the server", | ||||
|     "show": "Show", | ||||
|     "showless": "Show less...", | ||||
|     "showmore": "Show more...", | ||||
|     "site": "Site", | ||||
|     "sitemaintenance": "The site is undergoing maintenance and is currently not available", | ||||
| @ -264,6 +271,7 @@ | ||||
|     "unlimited": "Unlimited", | ||||
|     "unzipping": "Unzipping", | ||||
|     "upgraderunning": "Site is being upgraded, please retry later.", | ||||
|     "user": "User", | ||||
|     "userdeleted": "This user account has been deleted", | ||||
|     "userdetails": "User details", | ||||
|     "usernotfullysetup": "User not fully set-up", | ||||
|  | ||||
| @ -376,13 +376,15 @@ export class CoreUtilsProvider { | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Flatten an object, moving subobjects' properties to the first level using dot notation. E.g.: | ||||
|      * {a: {b: 1, c: 2}, d: 3} -> {'a.b': 1, 'a.c': 2, d: 3} | ||||
|      * Flatten an object, moving subobjects' properties to the first level. | ||||
|      * It supports 2 notations: dot notation and square brackets. | ||||
|      * E.g.: {a: {b: 1, c: 2}, d: 3} -> {'a.b': 1, 'a.c': 2, d: 3} | ||||
|      * | ||||
|      * @param {object} obj Object to flatten. | ||||
|      * @return {object} Flatten object. | ||||
|      * @param {boolean} [useDotNotation] Whether to use dot notation '.' or square brackets '['. | ||||
|      * @return {object} Flattened object. | ||||
|      */ | ||||
|     flattenObject(obj: object): object { | ||||
|     flattenObject(obj: object, useDotNotation?: boolean): object { | ||||
|         const toReturn = {}; | ||||
| 
 | ||||
|         for (const name in obj) { | ||||
| @ -398,7 +400,8 @@ export class CoreUtilsProvider { | ||||
|                         continue; | ||||
|                     } | ||||
| 
 | ||||
|                     toReturn[name + '.' + subName] = flatObject[subName]; | ||||
|                     const newName = useDotNotation ? name + '.' + subName : name + '[' + subName + ']'; | ||||
|                     toReturn[newName] = flatObject[subName]; | ||||
|                 } | ||||
|             } else { | ||||
|                 toReturn[name] = value; | ||||
| @ -1051,6 +1054,37 @@ export class CoreUtilsProvider { | ||||
|         return mapped; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Convert an object to a format of GET param. E.g.: {a: 1, b: 2} -> a=1&b=2 | ||||
|      * | ||||
|      * @param {any} object Object to convert. | ||||
|      * @param {boolean} [removeEmpty=true] Whether to remove params whose value is empty/null/undefined. | ||||
|      * @return {string} GET params. | ||||
|      */ | ||||
|     objectToGetParams(object: any, removeEmpty: boolean = true): string { | ||||
|         // First of all, flatten the object so all properties are in the first level.
 | ||||
|         const flattened = this.flattenObject(object); | ||||
|         let result = '', | ||||
|             joinChar = ''; | ||||
| 
 | ||||
|         for (const name in flattened) { | ||||
|             let value = flattened[name]; | ||||
| 
 | ||||
|             if (removeEmpty && (value === null || typeof value == 'undefined' || value === '')) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             if (typeof value == 'boolean') { | ||||
|                 value = value ? 1 : 0; | ||||
|             } | ||||
| 
 | ||||
|             result += joinChar + name + '=' + value; | ||||
|             joinChar = '&'; | ||||
|         } | ||||
| 
 | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Add a prefix to all the keys in an object. | ||||
|      * | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user