Merge pull request #1935 from albertgasset/MOBILE-3018

MOBILE-3018 forum: Post a copy to all groups
main
Juan Leyva 2019-05-23 17:00:14 +02:00 committed by GitHub
commit 4d6e019c75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 235 additions and 134 deletions

View File

@ -503,6 +503,7 @@
"addon.mod_forum.erroremptysubject": "forum", "addon.mod_forum.erroremptysubject": "forum",
"addon.mod_forum.errorgetforum": "local_moodlemobileapp", "addon.mod_forum.errorgetforum": "local_moodlemobileapp",
"addon.mod_forum.errorgetgroups": "local_moodlemobileapp", "addon.mod_forum.errorgetgroups": "local_moodlemobileapp",
"addon.mod_forum.errorposttoallgroups": "local_moodlemobileapp",
"addon.mod_forum.favouriteupdated": "forum", "addon.mod_forum.favouriteupdated": "forum",
"addon.mod_forum.forumnodiscussionsyet": "local_moodlemobileapp", "addon.mod_forum.forumnodiscussionsyet": "local_moodlemobileapp",
"addon.mod_forum.group": "local_moodlemobileapp", "addon.mod_forum.group": "local_moodlemobileapp",
@ -519,6 +520,7 @@
"addon.mod_forum.pinupdated": "forum", "addon.mod_forum.pinupdated": "forum",
"addon.mod_forum.postisprivatereply": "forum", "addon.mod_forum.postisprivatereply": "forum",
"addon.mod_forum.posttoforum": "forum", "addon.mod_forum.posttoforum": "forum",
"addon.mod_forum.posttomygroups": "forum",
"addon.mod_forum.privatereply": "forum", "addon.mod_forum.privatereply": "forum",
"addon.mod_forum.re": "forum", "addon.mod_forum.re": "forum",
"addon.mod_forum.refreshdiscussions": "local_moodlemobileapp", "addon.mod_forum.refreshdiscussions": "local_moodlemobileapp",
@ -1219,6 +1221,7 @@
"core.agelocationverification": "moodle", "core.agelocationverification": "moodle",
"core.ago": "message", "core.ago": "message",
"core.all": "moodle", "core.all": "moodle",
"core.allgroups": "moodle",
"core.allparticipants": "moodle", "core.allparticipants": "moodle",
"core.android": "local_moodlemobileapp", "core.android": "local_moodlemobileapp",
"core.answer": "moodle", "core.answer": "moodle",

View File

@ -31,7 +31,7 @@
<ion-icon name="information-circle"></ion-icon> {{ availabilityMessage }} <ion-icon name="information-circle"></ion-icon> {{ availabilityMessage }}
</ion-card> </ion-card>
<core-empty-box *ngIf="forum && discussions.length == 0" icon="chatbubbles" [message]="'addon.mod_forum.forumnodiscussionsyet' | translate"> <core-empty-box *ngIf="forum && discussions.length == 0 && offlineDiscussions.length == 0" icon="chatbubbles" [message]="'addon.mod_forum.forumnodiscussionsyet' | translate">
</core-empty-box> </core-empty-box>
<div text-wrap *ngIf="sortingAvailable && selectedSortOrder" ion-row padding-horizontal padding-top margin-bottom> <div text-wrap *ngIf="sortingAvailable && selectedSortOrder" ion-row padding-horizontal padding-top margin-bottom>
@ -41,7 +41,7 @@
</button> </button>
</div> </div>
<ng-container *ngIf="forum && discussions.length > 0"> <ng-container *ngIf="forum">
<ion-card *ngFor="let discussion of offlineDiscussions" (click)="openNewDiscussion(discussion.timecreated)" [class.addon-forum-discussion-selected]="discussion.timecreated == -selectedDiscussion"> <ion-card *ngFor="let discussion of offlineDiscussions" (click)="openNewDiscussion(discussion.timecreated)" [class.addon-forum-discussion-selected]="discussion.timecreated == -selectedDiscussion">
<ion-item text-wrap> <ion-item text-wrap>
<ion-avatar core-user-avatar [user]="discussion" item-start [courseId]="courseId"></ion-avatar> <ion-avatar core-user-avatar [user]="discussion" item-start [courseId]="courseId"></ion-avatar>

View File

@ -24,6 +24,7 @@
"erroremptysubject": "Post subject cannot be empty.", "erroremptysubject": "Post subject cannot be empty.",
"errorgetforum": "Error getting forum data.", "errorgetforum": "Error getting forum data.",
"errorgetgroups": "Error getting group settings.", "errorgetgroups": "Error getting group settings.",
"errorposttoallgroups": "Could not create new discussion in all groups.",
"favouriteupdated": "Your star option has been updated.", "favouriteupdated": "Your star option has been updated.",
"forumnodiscussionsyet": "There are no discussions yet in this forum.", "forumnodiscussionsyet": "There are no discussions yet in this forum.",
"group": "Group", "group": "Group",
@ -40,6 +41,7 @@
"pinupdated": "The pin option has been updated.", "pinupdated": "The pin option has been updated.",
"postisprivatereply": "This post was made privately and is not visible to all users.", "postisprivatereply": "This post was made privately and is not visible to all users.",
"posttoforum": "Post to forum", "posttoforum": "Post to forum",
"posttomygroups": "Post a copy to all groups",
"privatereply": "Reply privately", "privatereply": "Reply privately",
"re": "Re:", "re": "Re:",
"refreshdiscussions": "Refresh discussions", "refreshdiscussions": "Refresh discussions",

View File

@ -27,9 +27,13 @@
{{ 'addon.mod_forum.advanced' | translate }} {{ 'addon.mod_forum.advanced' | translate }}
</ion-item-divider> </ion-item-divider>
<ng-container *ngIf="advanced"> <ng-container *ngIf="advanced">
<ion-item *ngIf="showGroups && groupIds.length > 1 && accessInfo.cancanposttomygroups">
<ion-label>{{ 'addon.mod_forum.posttomygroups' | translate }}</ion-label>
<ion-toggle [(ngModel)]="newDiscussion.postToAllGroups"></ion-toggle>
</ion-item>
<ion-item *ngIf="showGroups"> <ion-item *ngIf="showGroups">
<ion-label id="addon-mod-forum-groupslabel">{{ 'addon.mod_forum.group' | translate }}</ion-label> <ion-label id="addon-mod-forum-groupslabel">{{ 'addon.mod_forum.group' | translate }}</ion-label>
<ion-select [(ngModel)]="newDiscussion.groupId" aria-labelledby="addon-mod-forum-groupslabel" interface="action-sheet"> <ion-select [(ngModel)]="newDiscussion.groupId" [disabled]="newDiscussion.postToAllGroups" aria-labelledby="addon-mod-forum-groupslabel" interface="action-sheet">
<ion-option *ngFor="let group of groups" [value]="group.id">{{ group.name }}</ion-option> <ion-option *ngFor="let group of groups" [value]="group.id">{{ group.name }}</ion-option>
</ion-select> </ion-select>
</ion-item> </ion-item>

View File

@ -53,15 +53,18 @@ export class AddonModForumNewDiscussionPage implements OnDestroy {
forum: any; forum: any;
showForm = false; showForm = false;
groups = []; groups = [];
groupIds = [];
newDiscussion = { newDiscussion = {
subject: '', subject: '',
message: null, // Null means empty or just white space. message: null, // Null means empty or just white space.
postToAllGroups: false,
groupId: 0, groupId: 0,
subscribe: true, subscribe: true,
pin: false, pin: false,
files: [] files: []
}; };
advanced = false; // Display all form fields. advanced = false; // Display all form fields.
accessInfo: any = {};
protected courseId: number; protected courseId: number;
protected cmId: number; protected cmId: number;
@ -146,9 +149,13 @@ export class AddonModForumNewDiscussionPage implements OnDestroy {
return promise.then((forumGroups) => { return promise.then((forumGroups) => {
if (forumGroups.length > 0) { if (forumGroups.length > 0) {
this.groups = forumGroups; this.groups = forumGroups;
this.groupIds = forumGroups.map((group) => group.id).filter((id) => id > 0);
// Do not override group id. // Do not override group id.
this.newDiscussion.groupId = this.newDiscussion.groupId || forumGroups[0].id; this.newDiscussion.groupId = this.newDiscussion.groupId || forumGroups[0].id;
this.showGroups = true; this.showGroups = true;
if (this.groupIds.length <= 1) {
this.newDiscussion.postToAllGroups = false;
}
} else { } else {
const message = mode === CoreGroupsProvider.SEPARATEGROUPS ? const message = mode === CoreGroupsProvider.SEPARATEGROUPS ?
'addon.mod_forum.cannotadddiscussionall' : 'addon.mod_forum.cannotadddiscussion'; 'addon.mod_forum.cannotadddiscussionall' : 'addon.mod_forum.cannotadddiscussion';
@ -159,6 +166,7 @@ export class AddonModForumNewDiscussionPage implements OnDestroy {
})); }));
} else { } else {
this.showGroups = false; this.showGroups = false;
this.newDiscussion.postToAllGroups = false;
// Use the canAddDiscussion WS to check if the user can add attachments and pin discussions. // Use the canAddDiscussion WS to check if the user can add attachments and pin discussions.
promises.push(this.forumProvider.canAddDiscussionToAll(this.forumId).then((response) => { promises.push(this.forumProvider.canAddDiscussionToAll(this.forumId).then((response) => {
@ -174,10 +182,18 @@ export class AddonModForumNewDiscussionPage implements OnDestroy {
this.forum = forum; this.forum = forum;
})); }));
// Get access information.
promises.push(this.forumProvider.getAccessInformation(this.forumId).then((accessInfo) => {
this.accessInfo = accessInfo;
}));
return Promise.all(promises);
}).then(() => {
// If editing a discussion, get offline data. // If editing a discussion, get offline data.
if (this.timeCreated && !refresh) { if (this.timeCreated && !refresh) {
this.syncId = this.forumSync.getForumSyncId(this.forumId); this.syncId = this.forumSync.getForumSyncId(this.forumId);
promises.push(this.forumSync.waitForSync(this.syncId).then(() => {
return this.forumSync.waitForSync(this.syncId).then(() => {
// Do not block if the scope is already destroyed. // Do not block if the scope is already destroyed.
if (!this.isDestroyed) { if (!this.isDestroyed) {
this.syncProvider.blockOperation(AddonModForumProvider.COMPONENT, this.syncId); this.syncProvider.blockOperation(AddonModForumProvider.COMPONENT, this.syncId);
@ -186,7 +202,13 @@ export class AddonModForumNewDiscussionPage implements OnDestroy {
return this.forumOffline.getNewDiscussion(this.forumId, this.timeCreated).then((discussion) => { return this.forumOffline.getNewDiscussion(this.forumId, this.timeCreated).then((discussion) => {
this.hasOffline = true; this.hasOffline = true;
discussion.options = discussion.options || {}; discussion.options = discussion.options || {};
this.newDiscussion.groupId = discussion.groupid ? discussion.groupid : this.newDiscussion.groupId; if (discussion.groupid == AddonModForumProvider.ALL_GROUPS) {
this.newDiscussion.groupId = this.groups[0].id;
this.newDiscussion.postToAllGroups = true;
} else {
this.newDiscussion.groupId = discussion.groupid;
this.newDiscussion.postToAllGroups = false;
}
this.newDiscussion.subject = discussion.subject; this.newDiscussion.subject = discussion.subject;
this.newDiscussion.message = discussion.message; this.newDiscussion.message = discussion.message;
this.newDiscussion.subscribe = discussion.options.discussionsubscribe; this.newDiscussion.subscribe = discussion.options.discussionsubscribe;
@ -203,15 +225,15 @@ export class AddonModForumNewDiscussionPage implements OnDestroy {
return Promise.resolve(promise).then(() => { return Promise.resolve(promise).then(() => {
// Show advanced fields by default if any of them has not the default value. // Show advanced fields by default if any of them has not the default value.
if (!this.newDiscussion.subscribe || this.newDiscussion.pin || this.newDiscussion.files.length) { if (!this.newDiscussion.subscribe || this.newDiscussion.pin || this.newDiscussion.files.length ||
this.groups.length > 0 && this.newDiscussion.groupId != this.groups[0].id ||
this.newDiscussion.postToAllGroups) {
this.advanced = true; this.advanced = true;
} }
}); });
}); });
})); });
} }
return Promise.all(promises);
}).then(() => { }).then(() => {
if (!this.originalData) { if (!this.originalData) {
// Initialize original data. // Initialize original data.
@ -232,9 +254,9 @@ export class AddonModForumNewDiscussionPage implements OnDestroy {
* Validate which of the groups returned by getActivityAllowedGroups in visible groups should be shown to post to. * Validate which of the groups returned by getActivityAllowedGroups in visible groups should be shown to post to.
* *
* @param {any[]} forumGroups Forum groups. * @param {any[]} forumGroups Forum groups.
* @return {Promise<any>} Promise resolved when done. * @return {Promise<any[]>} Promise resolved with the list of groups.
*/ */
protected validateVisibleGroups(forumGroups: any[]): Promise<any> { protected validateVisibleGroups(forumGroups: any[]): Promise<any[]> {
// We first check if the user can post to all the groups. // We first check if the user can post to all the groups.
return this.forumProvider.canAddDiscussionToAll(this.forumId).catch(() => { return this.forumProvider.canAddDiscussionToAll(this.forumId).catch(() => {
// The call failed, let's assume he can't. // The call failed, let's assume he can't.
@ -331,7 +353,7 @@ export class AddonModForumNewDiscussionPage implements OnDestroy {
if (canAdd) { if (canAdd) {
groups.unshift({ groups.unshift({
courseid: this.courseId, courseid: this.courseId,
id: -1, id: AddonModForumProvider.ALL_PARTICIPANTS,
name: this.translate.instant('core.allparticipants') name: this.translate.instant('core.allparticipants')
}); });
} }
@ -362,14 +384,14 @@ export class AddonModForumNewDiscussionPage implements OnDestroy {
/** /**
* Convenience function to update or return to discussions depending on device. * Convenience function to update or return to discussions depending on device.
* *
* @param {number} [discussionId] Id of the new discussion. * @param {number} [discussionIds] Ids of the new discussions.
* @param {number} [discTimecreated] The time created of the discussion (if offline). * @param {number} [discTimecreated] The time created of the discussion (if offline).
*/ */
protected returnToDiscussions(discussionId?: number, discTimecreated?: number): void { protected returnToDiscussions(discussionIds?: number[], discTimecreated?: number): void {
const data: any = { const data: any = {
forumId: this.forumId, forumId: this.forumId,
cmId: this.cmId, cmId: this.cmId,
discussionId: discussionId, discussionIds: discussionIds,
discTimecreated: discTimecreated discTimecreated: discTimecreated
}; };
this.eventsProvider.trigger(AddonModForumProvider.NEW_DISCUSSION_EVENT, data, this.sitesProvider.getCurrentSiteId()); this.eventsProvider.trigger(AddonModForumProvider.NEW_DISCUSSION_EVENT, data, this.sitesProvider.getCurrentSiteId());
@ -383,6 +405,7 @@ export class AddonModForumNewDiscussionPage implements OnDestroy {
this.newDiscussion.subject = ''; this.newDiscussion.subject = '';
this.newDiscussion.message = null; this.newDiscussion.message = null;
this.newDiscussion.files = []; this.newDiscussion.files = [];
this.newDiscussion.postToAllGroups = false;
this.messageEditor.clearText(); this.messageEditor.clearText();
this.originalData = this.utils.clone(this.newDiscussion); this.originalData = this.utils.clone(this.newDiscussion);
@ -414,13 +437,11 @@ export class AddonModForumNewDiscussionPage implements OnDestroy {
const subject = this.newDiscussion.subject; const subject = this.newDiscussion.subject;
let message = this.newDiscussion.message; let message = this.newDiscussion.message;
const pin = this.newDiscussion.pin; const pin = this.newDiscussion.pin;
const groupId = this.newDiscussion.groupId;
const attachments = this.newDiscussion.files; const attachments = this.newDiscussion.files;
const discTimecreated = this.timeCreated || Date.now(); const discTimecreated = this.timeCreated || Date.now();
const options: any = { const options: any = {
discussionsubscribe: !!this.newDiscussion.subscribe discussionsubscribe: !!this.newDiscussion.subscribe
}; };
let saveOffline = false;
if (!subject) { if (!subject) {
this.domUtils.showErrorModal('addon.mod_forum.erroremptysubject', true); this.domUtils.showErrorModal('addon.mod_forum.erroremptysubject', true);
@ -434,51 +455,29 @@ export class AddonModForumNewDiscussionPage implements OnDestroy {
} }
const modal = this.domUtils.showModalLoading('core.sending', true); const modal = this.domUtils.showModalLoading('core.sending', true);
let promise;
// Add some HTML to the message if needed. // Add some HTML to the message if needed.
message = this.textUtils.formatHtmlLines(message); message = this.textUtils.formatHtmlLines(message);
// Upload attachments first if any.
if (attachments.length) {
promise = this.forumHelper.uploadOrStoreNewDiscussionFiles(this.forumId, discTimecreated, attachments, false)
.catch(() => {
// Cannot upload them in online, save them in offline.
saveOffline = true;
return this.forumHelper.uploadOrStoreNewDiscussionFiles(this.forumId, discTimecreated, attachments, true);
});
} else {
promise = Promise.resolve();
}
promise.then((attach) => {
if (attach) {
options.attachmentsid = attach;
}
if (pin) { if (pin) {
options.discussionpinned = true; options.discussionpinned = true;
} }
if (saveOffline) { const groupIds = this.newDiscussion.postToAllGroups ? this.groupIds : [this.newDiscussion.groupId];
// Save discussion in offline.
return this.forumOffline.addNewDiscussion(this.forumId, forumName, this.courseId, subject, this.forumHelper.addNewDiscussion(this.forumId, forumName, this.courseId, subject, message, attachments, options, groupIds,
message, options, groupId, discTimecreated).then(() => { discTimecreated).then((discussionIds) => {
// Don't return anything. if (discussionIds) {
});
} else {
// Try to send it to server.
// Don't allow offline if there are attachments since they were uploaded fine.
return this.forumProvider.addNewDiscussion(this.forumId, forumName, this.courseId, subject, message, options,
groupId, undefined, discTimecreated, !attachments.length);
}
}).then((discussionId) => {
if (discussionId) {
// Data sent to server, delete stored files (if any). // Data sent to server, delete stored files (if any).
this.forumHelper.deleteNewDiscussionStoredFiles(this.forumId, discTimecreated); this.forumHelper.deleteNewDiscussionStoredFiles(this.forumId, discTimecreated);
} }
this.returnToDiscussions(discussionId, discTimecreated); if (discussionIds && discussionIds.length < groupIds.length) {
// Some discussions could not be created.
this.domUtils.showErrorModalDefault(null, 'addon.mod_forum.errorposttoallgroups', true);
}
this.returnToDiscussions(discussionIds, discTimecreated);
}).catch((message) => { }).catch((message) => {
this.domUtils.showErrorModalDefault(message, 'addon.mod_forum.cannotcreatediscussion', true); this.domUtils.showErrorModalDefault(message, 'addon.mod_forum.cannotcreatediscussion', true);
}).finally(() => { }).finally(() => {

View File

@ -46,6 +46,9 @@ export class AddonModForumProvider {
static SORTORDER_REPLIES_DESC = 5; static SORTORDER_REPLIES_DESC = 5;
static SORTORDER_REPLIES_ASC = 6; static SORTORDER_REPLIES_ASC = 6;
static ALL_PARTICIPANTS = -1;
static ALL_GROUPS = -2;
protected ROOT_CACHE_KEY = 'mmaModForum:'; protected ROOT_CACHE_KEY = 'mmaModForum:';
constructor(private appProvider: CoreAppProvider, constructor(private appProvider: CoreAppProvider,
@ -126,62 +129,6 @@ export class AddonModForumProvider {
return key; return key;
} }
/**
* Add a new discussion.
*
* @param {number} forumId Forum ID.
* @param {string} name Forum name.
* @param {number} courseId Course ID the forum belongs to.
* @param {string} subject New discussion's subject.
* @param {string} message New discussion's message.
* @param {any} [options] Options (subscribe, pin, ...).
* @param {string} [groupId] Group this discussion belongs to.
* @param {string} [siteId] Site ID. If not defined, current site.
* @param {number} [timeCreated] The time the discussion was created. Only used when editing discussion.
* @param {boolean} allowOffline True if it can be stored in offline, false otherwise.
* @return {Promise<any>} Promise resolved with discussion ID if sent online, resolved with false if stored offline.
*/
addNewDiscussion(forumId: number, name: string, courseId: number, subject: string, message: string, options?: any,
groupId?: number, siteId?: string, timeCreated?: number, allowOffline?: boolean): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
// Convenience function to store a message to be synchronized later.
const storeOffline = (): Promise<any> => {
return this.forumOffline.addNewDiscussion(forumId, name, courseId, subject, message, options,
groupId, timeCreated, siteId).then(() => {
return false;
});
};
// If we are editing an offline discussion, discard previous first.
let discardPromise;
if (timeCreated) {
discardPromise = this.forumOffline.deleteNewDiscussion(forumId, timeCreated, siteId);
} else {
discardPromise = Promise.resolve();
}
return discardPromise.then(() => {
if (!this.appProvider.isOnline() && allowOffline) {
// App is offline, store the action.
return storeOffline();
}
return this.addNewDiscussionOnline(forumId, subject, message, options, groupId, siteId).then((id) => {
// Success, return the discussion ID.
return id;
}).catch((error) => {
if (!allowOffline || this.utils.isWebServiceError(error)) {
// The WebService has thrown an error or offline not supported, reject.
return Promise.reject(error);
}
// Couldn't connect to server, store in offline.
return storeOffline();
});
});
}
/** /**
* Add a new discussion. It will fail if offline or cannot connect. * Add a new discussion. It will fail if offline or cannot connect.
* *
@ -268,7 +215,7 @@ export class AddonModForumProvider {
* - cancreateattachment (boolean) * - cancreateattachment (boolean)
*/ */
canAddDiscussionToAll(forumId: number): Promise<any> { canAddDiscussionToAll(forumId: number): Promise<any> {
return this.canAddDiscussion(forumId, -1); return this.canAddDiscussion(forumId, AddonModForumProvider.ALL_PARTICIPANTS);
} }
/** /**
@ -309,6 +256,7 @@ export class AddonModForumProvider {
return this.groupsProvider.getActivityAllowedGroups(cmId).then((forumGroups) => { return this.groupsProvider.getActivityAllowedGroups(cmId).then((forumGroups) => {
const strAllParts = this.translate.instant('core.allparticipants'); const strAllParts = this.translate.instant('core.allparticipants');
const strAllGroups = this.translate.instant('core.allgroups');
// Turn groups into an object where each group is identified by id. // Turn groups into an object where each group is identified by id.
const groups = {}; const groups = {};
@ -318,8 +266,11 @@ export class AddonModForumProvider {
// Format discussions. // Format discussions.
discussions.forEach((disc) => { discussions.forEach((disc) => {
if (disc.groupid === -1) { if (disc.groupid == AddonModForumProvider.ALL_PARTICIPANTS) {
disc.groupname = strAllParts; disc.groupname = strAllParts;
} else if (disc.groupid == AddonModForumProvider.ALL_GROUPS) {
// Offline discussions only.
disc.groupname = strAllGroups;
} else { } else {
const group = groups[disc.groupid]; const group = groups[disc.groupid];
if (group) { if (group) {

View File

@ -14,10 +14,12 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { CoreAppProvider } from '@providers/app';
import { CoreFileProvider } from '@providers/file'; import { CoreFileProvider } from '@providers/file';
import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader'; import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader';
import { CoreSitesProvider } from '@providers/sites'; import { CoreSitesProvider } from '@providers/sites';
import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreTimeUtilsProvider } from '@providers/utils/time';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreUserProvider } from '@core/user/providers/user'; import { CoreUserProvider } from '@core/user/providers/user';
import { AddonModForumProvider } from './forum'; import { AddonModForumProvider } from './forum';
import { AddonModForumOfflineProvider } from './offline'; import { AddonModForumOfflineProvider } from './offline';
@ -33,9 +35,123 @@ export class AddonModForumHelperProvider {
private uploaderProvider: CoreFileUploaderProvider, private uploaderProvider: CoreFileUploaderProvider,
private timeUtils: CoreTimeUtilsProvider, private timeUtils: CoreTimeUtilsProvider,
private userProvider: CoreUserProvider, private userProvider: CoreUserProvider,
private appProvider: CoreAppProvider,
private utils: CoreUtilsProvider,
private forumProvider: AddonModForumProvider, private forumProvider: AddonModForumProvider,
private forumOffline: AddonModForumOfflineProvider) {} private forumOffline: AddonModForumOfflineProvider) {}
/**
* Add a new discussion.
*
* @param {number} forumId Forum ID.
* @param {string} name Forum name.
* @param {number} courseId Course ID the forum belongs to.
* @param {string} subject New discussion's subject.
* @param {string} message New discussion's message.
* @param {any[]} [attachments] New discussion's attachments.
* @param {any} [options] Options (subscribe, pin, ...).
* @param {number[]} [groupIds] Groups this discussion belongs to.
* @param {number} [timeCreated] The time the discussion was created. Only used when editing discussion.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<number[]>} Promise resolved with ids of the created discussions or null if stored offline
*/
addNewDiscussion(forumId: number, name: string, courseId: number, subject: string, message: string, attachments?: any[],
options?: any, groupIds?: number[], timeCreated?: number, siteId?: string): Promise<number[]> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
groupIds = groupIds && groupIds.length > 0 ? groupIds : [0];
let saveOffline = false;
const attachmentsIds = [];
let offlineAttachments: any;
// Convenience function to store a message to be synchronized later.
const storeOffline = (): Promise<number[]> => {
// Multiple groups, the discussion is being posted to all groups.
const groupId = groupIds.length > 1 ? AddonModForumProvider.ALL_GROUPS : groupIds[0];
if (offlineAttachments) {
options.attachmentsid = offlineAttachments;
}
return this.forumOffline.addNewDiscussion(forumId, name, courseId, subject, message, options,
groupId, timeCreated, siteId).then(() => {
return null;
});
};
// First try to upload attachments, once per group.
let promise;
if (attachments && attachments.length > 0) {
const promises = groupIds.map(() => {
return this.uploadOrStoreNewDiscussionFiles(forumId, timeCreated, attachments, false).then((attach) => {
attachmentsIds.push(attach);
});
});
promise = Promise.all(promises).catch(() => {
// Cannot upload them in online, save them in offline.
saveOffline = true;
return this.uploadOrStoreNewDiscussionFiles(forumId, timeCreated, attachments, true).then((attach) => {
offlineAttachments = attach;
});
});
} else {
promise = Promise.resolve();
}
return promise.then(() => {
// If we are editing an offline discussion, discard previous first.
let discardPromise;
if (timeCreated) {
discardPromise = this.forumOffline.deleteNewDiscussion(forumId, timeCreated, siteId);
} else {
discardPromise = Promise.resolve();
}
return discardPromise.then(() => {
if (saveOffline || !this.appProvider.isOnline()) {
return storeOffline();
}
const errors = [];
const discussionIds = [];
const promises = groupIds.map((groupId, index) => {
const grouOptions = this.utils.clone(options);
if (attachmentsIds[index]) {
grouOptions.attachmentsid = attachmentsIds[index];
}
return this.forumProvider.addNewDiscussionOnline(forumId, subject, message, grouOptions, groupId, siteId)
.then((discussionId) => {
discussionIds.push(discussionId);
}).catch((error) => {
errors.push(error);
});
});
return Promise.all(promises).then(() => {
if (errors.length == groupIds.length) {
// All requests have failed.
for (let i = 0; i < errors.length; i++) {
if (this.utils.isWebServiceError(errors[i]) || attachments.length > 0) {
// The WebService has thrown an error or offline not supported, reject.
return Promise.reject(errors[i]);
}
}
// Couldn't connect to server, store offline.
return storeOffline();
}
return discussionIds;
});
});
});
}
/** /**
* Convert offline reply to online format in order to be compatible with them. * Convert offline reply to online format in order to be compatible with them.
* *

View File

@ -16,6 +16,7 @@ import { Injectable } from '@angular/core';
import { CoreFileProvider } from '@providers/file'; import { CoreFileProvider } from '@providers/file';
import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites';
import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTextUtilsProvider } from '@providers/utils/text';
import { AddonModForumProvider } from './forum';
/** /**
* Service to handle offline forum. * Service to handle offline forum.
@ -248,7 +249,7 @@ export class AddonModForumOfflineProvider {
subject: subject, subject: subject,
message: message, message: message,
options: JSON.stringify(options || {}), options: JSON.stringify(options || {}),
groupid: groupId || -1, groupid: groupId || AddonModForumProvider.ALL_PARTICIPANTS,
userid: userId || site.getUserId(), userid: userId || site.getUserId(),
timecreated: timeCreated || new Date().getTime() timecreated: timeCreated || new Date().getTime()
}; };

View File

@ -21,6 +21,7 @@ import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploa
import { CoreAppProvider } from '@providers/app'; import { CoreAppProvider } from '@providers/app';
import { CoreLoggerProvider } from '@providers/logger'; import { CoreLoggerProvider } from '@providers/logger';
import { CoreEventsProvider } from '@providers/events'; import { CoreEventsProvider } from '@providers/events';
import { CoreGroupsProvider } from '@providers/groups';
import { CoreSitesProvider } from '@providers/sites'; import { CoreSitesProvider } from '@providers/sites';
import { CoreSyncProvider } from '@providers/sync'; import { CoreSyncProvider } from '@providers/sync';
import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTextUtilsProvider } from '@providers/utils/text';
@ -46,6 +47,7 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider {
appProvider: CoreAppProvider, appProvider: CoreAppProvider,
courseProvider: CoreCourseProvider, courseProvider: CoreCourseProvider,
private eventsProvider: CoreEventsProvider, private eventsProvider: CoreEventsProvider,
private groupsProvider: CoreGroupsProvider,
loggerProvider: CoreLoggerProvider, loggerProvider: CoreLoggerProvider,
sitesProvider: CoreSitesProvider, sitesProvider: CoreSitesProvider,
syncProvider: CoreSyncProvider, syncProvider: CoreSyncProvider,
@ -222,38 +224,57 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider {
const promises = []; const promises = [];
discussions.forEach((data) => { discussions.forEach((data) => {
data.options = data.options || {}; let groupsPromise;
if (data.groupid == AddonModForumProvider.ALL_GROUPS) {
// First of all upload the attachments (if any). // Fetch all group ids.
const promise = this.uploadAttachments(forumId, data, true, siteId, userId).then((itemId) => { groupsPromise = this.forumProvider.getForumById(data.courseid, data.forumid, siteId).then((forum) => {
// Now try to add the discussion. return this.groupsProvider.getActivityAllowedGroups(forum.cmid).then((groups) => {
data.options.attachmentsid = itemId; return groups.map((group) => group.id);
return this.forumProvider.addNewDiscussionOnline(forumId, data.subject, data.message,
data.options, data.groupid, siteId);
}); });
});
} else {
groupsPromise = Promise.resolve([data.groupid]);
}
promises.push(promise.then(() => { promises.push(groupsPromise.then((groupIds) => {
result.updated = true; const errors = [];
return this.deleteNewDiscussion(forumId, data.timecreated, siteId, userId); return Promise.all(groupIds.map((groupId) => {
// First of all upload the attachments (if any).
return this.uploadAttachments(forumId, data, true, siteId, userId).then((itemId) => {
// Now try to add the discussion.
const options = this.utils.clone(data.options || {});
options.attachmentsid = itemId;
return this.forumProvider.addNewDiscussionOnline(forumId, data.subject, data.message, options,
groupId, siteId);
}).catch((error) => { }).catch((error) => {
if (this.utils.isWebServiceError(error)) { errors.push(error);
// The WebService has thrown an error, this means that responses cannot be submitted. Delete them. });
})).then(() => {
if (errors.length == groupIds.length) {
// All requests have failed, reject if errors were not returned by WS.
for (let i = 0; i < errors.length; i++) {
if (!this.utils.isWebServiceError(errors[i])) {
return Promise.reject(errors[i]);
}
}
}
// All requests succeeded, some failed or all failed with a WS error.
result.updated = true; result.updated = true;
return this.deleteNewDiscussion(forumId, data.timecreated, siteId, userId).then(() => { return this.deleteNewDiscussion(forumId, data.timecreated, siteId, userId).then(() => {
// Responses deleted, add a warning. if (errors.length == groupIds.length) {
// All requests failed with WS error.
result.warnings.push(this.translate.instant('core.warningofflinedatadeleted', { result.warnings.push(this.translate.instant('core.warningofflinedatadeleted', {
component: this.componentTranslate, component: this.componentTranslate,
name: data.name, name: data.name,
error: this.textUtils.getErrorMessageFromError(error) error: this.textUtils.getErrorMessageFromError(errors[0])
})); }));
});
} else {
// Couldn't connect to server, reject.
return Promise.reject(error);
} }
});
});
})); }));
}); });

View File

@ -503,6 +503,7 @@
"addon.mod_forum.erroremptysubject": "Post subject cannot be empty.", "addon.mod_forum.erroremptysubject": "Post subject cannot be empty.",
"addon.mod_forum.errorgetforum": "Error getting forum data.", "addon.mod_forum.errorgetforum": "Error getting forum data.",
"addon.mod_forum.errorgetgroups": "Error getting group settings.", "addon.mod_forum.errorgetgroups": "Error getting group settings.",
"addon.mod_forum.errorposttoallgroups": "Could not create new discussion in all groups.",
"addon.mod_forum.favouriteupdated": "Your star option has been updated.", "addon.mod_forum.favouriteupdated": "Your star option has been updated.",
"addon.mod_forum.forumnodiscussionsyet": "There are no discussions yet in this forum.", "addon.mod_forum.forumnodiscussionsyet": "There are no discussions yet in this forum.",
"addon.mod_forum.group": "Group", "addon.mod_forum.group": "Group",
@ -519,6 +520,7 @@
"addon.mod_forum.pinupdated": "The pin option has been updated.", "addon.mod_forum.pinupdated": "The pin option has been updated.",
"addon.mod_forum.postisprivatereply": "This post was made privately and is not visible to all users.", "addon.mod_forum.postisprivatereply": "This post was made privately and is not visible to all users.",
"addon.mod_forum.posttoforum": "Post to forum", "addon.mod_forum.posttoforum": "Post to forum",
"addon.mod_forum.posttomygroups": "Post a copy to all groups",
"addon.mod_forum.privatereply": "Reply privately", "addon.mod_forum.privatereply": "Reply privately",
"addon.mod_forum.re": "Re:", "addon.mod_forum.re": "Re:",
"addon.mod_forum.refreshdiscussions": "Refresh discussions", "addon.mod_forum.refreshdiscussions": "Refresh discussions",
@ -1219,6 +1221,7 @@
"core.agelocationverification": "Age and location verification", "core.agelocationverification": "Age and location verification",
"core.ago": "{{$a}} ago", "core.ago": "{{$a}} ago",
"core.all": "All", "core.all": "All",
"core.allgroups": "All groups",
"core.allparticipants": "All participants", "core.allparticipants": "All participants",
"core.android": "Android", "core.android": "Android",
"core.answer": "Answer", "core.answer": "Answer",

View File

@ -4,6 +4,7 @@
"agelocationverification": "Age and location verification", "agelocationverification": "Age and location verification",
"ago": "{{$a}} ago", "ago": "{{$a}} ago",
"all": "All", "all": "All",
"allgroups": "All groups",
"allparticipants": "All participants", "allparticipants": "All participants",
"android": "Android", "android": "Android",
"answer": "Answer", "answer": "Answer",