diff --git a/scripts/langindex.json b/scripts/langindex.json index 52408d77d..d2658bf07 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -494,11 +494,13 @@ "addon.mod_forum.errorgetgroups": "local_moodlemobileapp", "addon.mod_forum.forumnodiscussionsyet": "local_moodlemobileapp", "addon.mod_forum.group": "local_moodlemobileapp", + "addon.mod_forum.locked": "forum", "addon.mod_forum.message": "forum", "addon.mod_forum.modeflatnewestfirst": "forum", "addon.mod_forum.modeflatoldestfirst": "forum", "addon.mod_forum.modenested": "forum", "addon.mod_forum.modulenameplural": "forum", + "addon.mod_forum.notlocked": "forum", "addon.mod_forum.numdiscussions": "local_moodlemobileapp", "addon.mod_forum.numreplies": "local_moodlemobileapp", "addon.mod_forum.postisprivatereply": "forum", diff --git a/src/addon/mod/forum/components/index/index.ts b/src/addon/mod/forum/components/index/index.ts index 999974f31..b3c8769e7 100644 --- a/src/addon/mod/forum/components/index/index.ts +++ b/src/addon/mod/forum/components/index/index.ts @@ -58,6 +58,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom protected replyObserver: any; protected newDiscObserver: any; protected viewDiscObserver: any; + protected changeDiscObserver: any; hasOfflineRatings: boolean; protected ratingOfflineObserver: any; @@ -94,6 +95,8 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom this.eventReceived.bind(this, true)); this.replyObserver = this.eventsProvider.on(AddonModForumProvider.REPLY_DISCUSSION_EVENT, this.eventReceived.bind(this, false)); + this.changeDiscObserver = this.eventsProvider.on(AddonModForumProvider.CHANGE_DISCUSSION_EVENT, + this.eventReceived.bind(this, false)); // Select the current opened discussion. this.viewDiscObserver = this.eventsProvider.on(AddonModForumProvider.VIEW_DISCUSSION_EVENT, (data) => { @@ -446,9 +449,8 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom courseId: this.courseId, cmId: this.module.id, forumId: this.forum.id, - discussionId: discussion.discussion, + discussion: discussion, trackPosts: this.trackPosts, - locked: discussion.locked }; this.splitviewCtrl.push('AddonModForumDiscussionPage', params); } @@ -480,6 +482,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom this.newDiscObserver && this.newDiscObserver.off(); this.replyObserver && this.replyObserver.off(); this.viewDiscObserver && this.viewDiscObserver.off(); + this.changeDiscObserver && this.changeDiscObserver.off(); this.ratingOfflineObserver && this.ratingOfflineObserver.off(); this.ratingSyncObserver && this.ratingSyncObserver.off(); } diff --git a/src/addon/mod/forum/lang/en.json b/src/addon/mod/forum/lang/en.json index 42fb302a5..ea720c2ff 100644 --- a/src/addon/mod/forum/lang/en.json +++ b/src/addon/mod/forum/lang/en.json @@ -18,11 +18,13 @@ "errorgetgroups": "Error getting group settings.", "forumnodiscussionsyet": "There are no discussions yet in this forum.", "group": "Group", + "locked": "Locked", "message": "Message", "modeflatnewestfirst": "Display replies flat, with newest first", "modeflatoldestfirst": "Display replies flat, with oldest first", "modenested": "Display replies in nested form", "modulenameplural": "Forums", + "notlocked": "Lock", "numdiscussions": "{{numdiscussions}} discussions", "numreplies": "{{numreplies}} replies", "postisprivatereply": "This post was made privately and is not visible to all users.", diff --git a/src/addon/mod/forum/pages/discussion/discussion.html b/src/addon/mod/forum/pages/discussion/discussion.html index 99adf295a..434ac76e2 100644 --- a/src/addon/mod/forum/pages/discussion/discussion.html +++ b/src/addon/mod/forum/pages/discussion/discussion.html @@ -13,6 +13,8 @@ + + @@ -26,7 +28,7 @@ {{ 'core.hasdatatosync' | translate:{$a: discussionStr} }} - + {{ 'addon.mod_forum.discussionlocked' | translate }} diff --git a/src/addon/mod/forum/pages/discussion/discussion.ts b/src/addon/mod/forum/pages/discussion/discussion.ts index 48c5949ba..8b3a81a42 100644 --- a/src/addon/mod/forum/pages/discussion/discussion.ts +++ b/src/addon/mod/forum/pages/discussion/discussion.ts @@ -54,7 +54,6 @@ export class AddonModForumDiscussionPage implements OnDestroy { defaultSubject: string; isOnline: boolean; isSplitViewOn: boolean; - locked: boolean; postHasOffline: boolean; sort: SortType = 'flat-oldest'; trackPosts: boolean; @@ -108,9 +107,9 @@ export class AddonModForumDiscussionPage implements OnDestroy { this.courseId = navParams.get('courseId'); this.cmId = navParams.get('cmId'); this.forumId = navParams.get('forumId'); - this.discussionId = navParams.get('discussionId'); + this.discussion = navParams.get('discussion'); + this.discussionId = this.discussion ? this.discussion.discussion : navParams.get('discussionId'); this.trackPosts = navParams.get('trackPosts'); - this.locked = navParams.get('locked'); this.postId = navParams.get('postId'); this.isOnline = this.appProvider.isOnline(); @@ -222,7 +221,7 @@ export class AddonModForumDiscussionPage implements OnDestroy { } /** - * Convenience function to get forum discussions. + * Convenience function to get the posts. * * @param {boolean} [sync] Whether to try to synchronize the discussion. * @param {boolean} [showErrors] Whether to show errors in a modal. @@ -243,11 +242,12 @@ export class AddonModForumDiscussionPage implements OnDestroy { let onlinePosts = []; const offlineReplies = []; let hasUnreadPosts = false; + let ratingInfo; return syncPromise.then(() => { return this.forumProvider.getDiscussionPosts(this.discussionId).then((response) => { onlinePosts = response.posts; - this.ratingInfo = response.ratinginfo; + ratingInfo = response.ratinginfo; }).then(() => { // Check if there are responses stored in offline. return this.forumOffline.getDiscussionReplies(this.discussionId).then((replies) => { @@ -285,34 +285,23 @@ export class AddonModForumDiscussionPage implements OnDestroy { }); }); }).then(() => { - const posts = offlineReplies.concat(onlinePosts); - this.discussion = this.forumProvider.extractStartingPost(posts); - - if (!this.discussion) { - return Promise.reject('Invalid forum discussion'); - } + let posts = offlineReplies.concat(onlinePosts); // If sort type is nested, normal sorting is disabled and nested posts will be displayed. if (this.sort == 'nested') { // Sort first by creation date to make format tree work. this.forumProvider.sortDiscussionPosts(posts, 'ASC'); - this.posts = this.utils.formatTree(posts, 'parent', 'id', this.discussion.id); + posts = this.utils.formatTree(posts, 'parent', 'id', this.discussion.id); } else { // Set default reply subject. const direction = this.sort == 'flat-newest' ? 'DESC' : 'ASC'; this.forumProvider.sortDiscussionPosts(posts, direction); - this.posts = posts; } this.defaultSubject = this.translate.instant('addon.mod_forum.re') + ' ' + this.discussion.subject; this.replyData.subject = this.defaultSubject; // Now try to get the forum. return this.fetchForum().then((forum) => { - if (this.discussion.userfullname && this.discussion.parent == 0 && forum.type == 'single') { - // Hide author for first post and type single. - this.discussion.userfullname = null; - } - // "forum.istracked" is more reliable than "trackPosts". if (typeof forum.istracked != 'undefined') { this.trackPosts = forum.istracked; @@ -321,14 +310,44 @@ export class AddonModForumDiscussionPage implements OnDestroy { this.forumId = forum.id; this.cmId = forum.cmid; this.forum = forum; - }).then(() => { - return this.forumProvider.getAccessInformation(this.forum.id).then((accessInfo) => { + + const promises = []; + + promises.push(this.forumProvider.getAccessInformation(this.forum.id).then((accessInfo) => { this.accessInfo = accessInfo; - }); + })); + + // Fetch the discussion if not passed as parameter. + if (!this.discussion) { + promises.push(this.forumHelper.getDiscussionById(forum.id, this.discussionId).then((discussion) => { + this.discussion = discussion; + }).catch(() => { + // Ignore errors. + })); + } + + return Promise.all(promises); }).catch(() => { // Ignore errors. this.forum = {}; this.accessInfo = {}; + }).then(() => { + const startingPost = this.forumProvider.extractStartingPost(posts); + if (startingPost) { + // Update discussion data from first post. + this.discussion = Object.assign(this.discussion || {}, startingPost); + } else if (!this.discussion) { + // The discussion object was not passed as parameter and there is no starting post. + return Promise.reject('Invalid forum discussion.'); + } + + if (this.discussion.userfullname && this.discussion.parent == 0 && this.forum.type == 'single') { + // Hide author for first post and type single. + this.discussion.userfullname = null; + } + + this.posts = posts; + this.ratingInfo = ratingInfo; }); }).then(() => { return this.ratingOffline.hasRatings('mod_forum', 'post', 'module', this.cmId, this.discussionId).then((hasRatings) => { @@ -454,6 +473,31 @@ export class AddonModForumDiscussionPage implements OnDestroy { return this.fetchPosts(); } + /** + * Lock or unlock the discussion. + * + * @param {boolean} locked True to lock the discussion, false to unlock. + */ + setLockState(locked: boolean): void { + const modal = this.domUtils.showModalLoading('core.sending', true); + + this.forumProvider.setLockState(this.forumId, this.discussionId, locked).then((response) => { + this.discussion.locked = response.locked; + + const data = { + forumId: this.forumId, + discussionId: this.discussionId, + cmId: this.cmId, + locked: this.discussion.locked + }; + this.eventsProvider.trigger(AddonModForumProvider.CHANGE_DISCUSSION_EVENT, data, this.sitesProvider.getCurrentSiteId()); + }).catch((error) => { + this.domUtils.showErrorModal(error); + }).finally(() => { + modal.dismiss(); + }); + } + /** * New post added. */ diff --git a/src/addon/mod/forum/providers/forum.ts b/src/addon/mod/forum/providers/forum.ts index e4bc9f978..520ed72c5 100644 --- a/src/addon/mod/forum/providers/forum.ts +++ b/src/addon/mod/forum/providers/forum.ts @@ -34,6 +34,7 @@ export class AddonModForumProvider { static NEW_DISCUSSION_EVENT = 'addon_mod_forum_new_discussion'; static REPLY_DISCUSSION_EVENT = 'addon_mod_forum_reply_discussion'; static VIEW_DISCUSSION_EVENT = 'addon_mod_forum_view_discussion'; + static CHANGE_DISCUSSION_EVENT = 'addon_mod_forum_lock_discussion'; static MARK_READ_EVENT = 'addon_mod_forum_mark_read'; protected ROOT_CACHE_KEY = 'mmaModForum:'; @@ -761,6 +762,28 @@ export class AddonModForumProvider { }); } + /** + * Lock or unlock a discussion. + * + * @param {number} forumId Forum id. + * @param {number} discussionId DIscussion id. + * @param {boolean} locked True to lock, false to unlock. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resvoled when done. + * @since 3.7 + */ + setLockState(forumId: number, discussionId: number, locked: boolean, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const params = { + forumid: forumId, + discussionid: discussionId, + targetstate: locked ? 0 : 1 + }; + + return site.write('mod_forum_set_lock_state', params); + }); + } + /** * Store the users data from a discussions/posts list. * diff --git a/src/addon/mod/forum/providers/helper.ts b/src/addon/mod/forum/providers/helper.ts index d33c6ef28..eeeb4a0f7 100644 --- a/src/addon/mod/forum/providers/helper.ts +++ b/src/addon/mod/forum/providers/helper.ts @@ -15,6 +15,7 @@ import { Injectable } from '@angular/core'; import { CoreFileProvider } from '@providers/file'; import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader'; +import { CoreSitesProvider } from '@providers/sites'; import { CoreUserProvider } from '@core/user/providers/user'; import { AddonModForumProvider } from './forum'; import { AddonModForumOfflineProvider } from './offline'; @@ -25,8 +26,10 @@ import { AddonModForumOfflineProvider } from './offline'; @Injectable() export class AddonModForumHelperProvider { constructor(private fileProvider: CoreFileProvider, + private sitesProvider: CoreSitesProvider, private uploaderProvider: CoreFileUploaderProvider, private userProvider: CoreUserProvider, + private forumProvider: AddonModForumProvider, private forumOffline: AddonModForumOfflineProvider) {} /** @@ -119,6 +122,38 @@ export class AddonModForumHelperProvider { }); } + /** + * Get a forum discussion by id. + * + * This function is inefficient because it needs to fetch all discussion pages in the worst case. + * + * @param {number} forumId Forum ID. + * @param {number} discussionId Discussion ID. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with the discussion data. + */ + getDiscussionById(forumId: number, discussionId: number, siteId?: string): Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + const findDiscussion = (page: number): Promise => { + return this.forumProvider.getDiscussions(forumId, page, false, siteId).then((response) => { + if (response.discussions && response.discussions.length > 0) { + const discussion = response.discussions.find((discussion) => discussion.id == discussionId); + if (discussion) { + return discussion; + } + if (response.canLoadMore) { + return findDiscussion(page + 1); + } + } + + return Promise.reject(null); + }); + }; + + return findDiscussion(0); + } + /** * Get a list of stored attachment files for a new discussion. See AddonModForumHelper#storeNewDiscussionFiles. * diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index 0f9984153..5cbef339b 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -494,11 +494,13 @@ "addon.mod_forum.errorgetgroups": "Error getting group settings.", "addon.mod_forum.forumnodiscussionsyet": "There are no discussions yet in this forum.", "addon.mod_forum.group": "Group", + "addon.mod_forum.locked": "Locked", "addon.mod_forum.message": "Message", "addon.mod_forum.modeflatnewestfirst": "Display replies flat, with newest first", "addon.mod_forum.modeflatoldestfirst": "Display replies flat, with oldest first", "addon.mod_forum.modenested": "Display replies in nested form", "addon.mod_forum.modulenameplural": "Forums", + "addon.mod_forum.notlocked": "Lock", "addon.mod_forum.numdiscussions": "{{numdiscussions}} discussions", "addon.mod_forum.numreplies": "{{numreplies}} replies", "addon.mod_forum.postisprivatereply": "This post was made privately and is not visible to all users.",