From 5445d24a5770239bff8346d3a161a9bf8e73d72b Mon Sep 17 00:00:00 2001
From: Dani Palou <dani@moodle.com>
Date: Tue, 19 Apr 2022 13:25:38 +0200
Subject: [PATCH 1/2] MOBILE-3833 siteplugins: Use theme icon if present for
 modules

---
 .../features/siteplugins/classes/handlers/module-handler.ts | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/core/features/siteplugins/classes/handlers/module-handler.ts b/src/core/features/siteplugins/classes/handlers/module-handler.ts
index 57b415309..5b90fbee7 100644
--- a/src/core/features/siteplugins/classes/handlers/module-handler.ts
+++ b/src/core/features/siteplugins/classes/handlers/module-handler.ts
@@ -69,12 +69,14 @@ export class CoreSitePluginsModuleHandler extends CoreSitePluginsBaseHandler imp
         sectionId?: number,
         forCoursePage?: boolean,
     ): Promise<CoreCourseModuleHandlerData> {
+        const icon = module.modicon || this.handlerSchema.displaydata?.icon; // Prioritize theme icon over handler icon.
+
         if (this.shouldOnlyDisplayDescription(module, forCoursePage)) {
             const title = module.description;
             module.description = '';
 
             return {
-                icon: await CoreCourse.getModuleIconSrc(module.modname, this.handlerSchema.displaydata?.icon),
+                icon: await CoreCourse.getModuleIconSrc(module.modname, icon),
                 title: title || '',
                 a11yTitle: '',
                 class: this.handlerSchema.displaydata?.class,
@@ -85,7 +87,7 @@ export class CoreSitePluginsModuleHandler extends CoreSitePluginsBaseHandler imp
         const showDowloadButton = this.handlerSchema.downloadbutton;
         const handlerData: CoreCourseModuleHandlerData = {
             title: module.name,
-            icon: await CoreCourse.getModuleIconSrc(module.modname, this.handlerSchema.displaydata?.icon),
+            icon: await CoreCourse.getModuleIconSrc(module.modname, icon),
             class: this.handlerSchema.displaydata?.class,
             showDownloadButton: showDowloadButton !== undefined ? showDowloadButton : hasOffline,
         };

From 679f2e592f28f2de77fae4d0be95944c44327004 Mon Sep 17 00:00:00 2001
From: Dani Palou <dani@moodle.com>
Date: Wed, 20 Apr 2022 09:22:16 +0200
Subject: [PATCH 2/2] MOBILE-3833 forum: Display post menu only if can edit or
 delete

---
 .../post-options-menu/post-options-menu.html  |  4 +-
 .../post-options-menu/post-options-menu.ts    | 59 ++++++++++---------
 src/addons/mod/forum/components/post/post.ts  | 11 +++-
 .../forum/pages/discussion/discussion.html    |  2 +
 .../forum/pages/discussion/discussion.page.ts |  2 +
 5 files changed, 46 insertions(+), 32 deletions(-)

diff --git a/src/addons/mod/forum/components/post-options-menu/post-options-menu.html b/src/addons/mod/forum/components/post-options-menu/post-options-menu.html
index 2ff947373..63660ebd7 100644
--- a/src/addons/mod/forum/components/post-options-menu/post-options-menu.html
+++ b/src/addons/mod/forum/components/post-options-menu/post-options-menu.html
@@ -1,11 +1,11 @@
 <core-loading [hideUntil]="loaded" [fullscreen]="false">
-    <ion-item button class="ion-text-wrap" (click)="editPost()" *ngIf="offlinePost || (canEdit && isOnline)" detail="false">
+    <ion-item button class="ion-text-wrap" (click)="editPost()" *ngIf="offlinePost || canEdit" detail="false">
         <ion-icon name="fas-pen" slot="start" aria-hidden="true"></ion-icon>
         <ion-label>
             <p class="item-heading">{{ 'addon.mod_forum.edit' | translate }}</p>
         </ion-label>
     </ion-item>
-    <ion-item button class="ion-text-wrap" (click)="deletePost()" *ngIf="offlinePost || (canDelete && isOnline)" detail="false">
+    <ion-item button class="ion-text-wrap" (click)="deletePost()" *ngIf="offlinePost || canDelete" detail="false">
         <ion-icon name="fas-trash" slot="start" aria-hidden="true"></ion-icon>
         <ion-label>
             <p class="item-heading" *ngIf="!offlinePost">{{ 'addon.mod_forum.delete' | translate }}</p>
diff --git a/src/addons/mod/forum/components/post-options-menu/post-options-menu.ts b/src/addons/mod/forum/components/post-options-menu/post-options-menu.ts
index 257d8ec78..02015d077 100644
--- a/src/addons/mod/forum/components/post-options-menu/post-options-menu.ts
+++ b/src/addons/mod/forum/components/post-options-menu/post-options-menu.ts
@@ -12,13 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import { Component, Input, OnDestroy, OnInit } from '@angular/core';
+import { Component, Input, OnInit } from '@angular/core';
 import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
 import { CoreApp } from '@services/app';
 import { AddonModForum, AddonModForumPost } from '@addons/mod/forum/services/forum';
-import { Network, NgZone, PopoverController } from '@singletons';
-import { Subscription } from 'rxjs';
+import { PopoverController } from '@singletons';
 import { CoreDomUtils } from '@services/utils/dom';
+import { CoreNetworkError } from '@classes/errors/network-error';
 
 /**
  * This component is meant to display a popover with the post options.
@@ -28,7 +28,7 @@ import { CoreDomUtils } from '@services/utils/dom';
     templateUrl: 'post-options-menu.html',
     styleUrls: ['./post-options-menu.scss'],
 })
-export class AddonModForumPostOptionsMenuComponent implements OnInit, OnDestroy {
+export class AddonModForumPostOptionsMenuComponent implements OnInit {
 
     @Input() post!: AddonModForumPost; // The post.
     @Input() cmId!: number;
@@ -38,32 +38,15 @@ export class AddonModForumPostOptionsMenuComponent implements OnInit, OnDestroy
     canDelete = false;
     loaded = false;
     url?: string;
-    isOnline!: boolean;
-    offlinePost!: boolean;
-
-    protected onlineObserver?: Subscription;
+    offlinePost = false;
 
     /**
-     * Component being initialized.
+     * @inheritdoc
      */
     async ngOnInit(): Promise<void> {
-        this.isOnline = CoreApp.isOnline();
-
-        this.onlineObserver = Network.onChange().subscribe(() => {
-            // Execute the callback in the Angular zone, so change detection doesn't stop working.
-            NgZone.run(() => {
-                this.isOnline = CoreApp.isOnline();
-            });
-        });
-
-        if (this.post.id > 0) {
-            const site = CoreSites.getRequiredCurrentSite();
-            this.url = site.createSiteUrl('/mod/forum/discuss.php', { d: this.post.discussionid.toString() }, 'p' + this.post.id);
-            this.offlinePost = false;
-        } else {
-            // Offline post, you can edit or discard the post.
+        this.offlinePost = this.post.id < 0;
+        if (this.offlinePost) {
             this.loaded = true;
-            this.offlinePost = true;
 
             return;
         }
@@ -81,6 +64,8 @@ export class AddonModForumPostOptionsMenuComponent implements OnInit, OnDestroy
                 }
             } else {
                 this.loaded = true;
+                // Display the open in browser button to prevent having an empty menu.
+                this.setOpenInBrowserUrl();
 
                 return;
             }
@@ -88,14 +73,20 @@ export class AddonModForumPostOptionsMenuComponent implements OnInit, OnDestroy
 
         this.canDelete = !!this.post.capabilities.delete && AddonModForum.isDeletePostAvailable();
         this.canEdit = !!this.post.capabilities.edit && AddonModForum.isUpdatePostAvailable();
+        if (!this.canDelete && !this.canEdit) {
+            // Display the open in browser button to prevent having an empty menu.
+            this.setOpenInBrowserUrl();
+        }
+
         this.loaded = true;
     }
 
     /**
-     * Component destroyed.
+     * Set the URL to open in browser.
      */
-    ngOnDestroy(): void {
-        this.onlineObserver?.unsubscribe();
+    protected setOpenInBrowserUrl(): void {
+        const site = CoreSites.getRequiredCurrentSite();
+        this.url = site.createSiteUrl('/mod/forum/discuss.php', { d: this.post.discussionid.toString() }, 'p' + this.post.id);
     }
 
     /**
@@ -110,6 +101,12 @@ export class AddonModForumPostOptionsMenuComponent implements OnInit, OnDestroy
      */
     deletePost(): void {
         if (!this.offlinePost) {
+            if (!CoreApp.isOnline()) {
+                CoreDomUtils.showErrorModal(new CoreNetworkError());
+
+                return;
+            }
+
             PopoverController.dismiss({ action: 'delete' });
         } else {
             PopoverController.dismiss({ action: 'deleteoffline' });
@@ -120,6 +117,12 @@ export class AddonModForumPostOptionsMenuComponent implements OnInit, OnDestroy
      * Edit a post.
      */
     editPost(): void {
+        if (!this.offlinePost && !CoreApp.isOnline()) {
+            CoreDomUtils.showErrorModal(new CoreNetworkError());
+
+            return;
+        }
+
         PopoverController.dismiss({ action: 'edit' });
     }
 
diff --git a/src/addons/mod/forum/components/post/post.ts b/src/addons/mod/forum/components/post/post.ts
index 5f0acd153..8ece16ae3 100644
--- a/src/addons/mod/forum/components/post/post.ts
+++ b/src/addons/mod/forum/components/post/post.ts
@@ -116,8 +116,15 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
         this.defaultReplySubject = this.post.replysubject || ((this.post.subject.startsWith('Re: ') ||
             this.post.subject.startsWith(reTranslated)) ? this.post.subject : `${reTranslated} ${this.post.subject}`);
 
-        this.optionsMenuEnabled = this.post.id < 0 || (AddonModForum.isGetDiscussionPostAvailable() &&
-                    (AddonModForum.isDeletePostAvailable() || AddonModForum.isUpdatePostAvailable()));
+        if (this.post.id < 0) {
+            this.optionsMenuEnabled = true;
+        } else if (this.post.capabilities.delete !== undefined) {
+            this.optionsMenuEnabled = this.post.capabilities.delete === true || this.post.capabilities.edit === true;
+        } else {
+            // Cannot know if the user can edit/delete or not, display the menu if the WebServices are available.
+            this.optionsMenuEnabled = this.post.id < 0 || (AddonModForum.isGetDiscussionPostAvailable() &&
+                        (AddonModForum.isDeletePostAvailable() || AddonModForum.isUpdatePostAvailable()));
+        }
     }
 
     /**
diff --git a/src/addons/mod/forum/pages/discussion/discussion.html b/src/addons/mod/forum/pages/discussion/discussion.html
index 1c5b8d781..d41503ce8 100644
--- a/src/addons/mod/forum/pages/discussion/discussion.html
+++ b/src/addons/mod/forum/pages/discussion/discussion.html
@@ -53,6 +53,8 @@
             [content]="'addon.mod_forum.removefromfavourites' | translate" iconAction="fas-star" [iconSlash]="true"
             (action)="toggleFavouriteState(false)">
         </core-context-menu-item>
+        <core-context-menu-item [hidden]="!externalUrl" [priority]="100" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
+            iconAction="fas-external-link-alt" [showBrowserWarning]="false"></core-context-menu-item>
     </core-context-menu>
 </core-navbar-buttons>
 <ion-content [core-swipe-navigation]="discussions" class="limited-width">
diff --git a/src/addons/mod/forum/pages/discussion/discussion.page.ts b/src/addons/mod/forum/pages/discussion/discussion.page.ts
index b7c88be26..2b422913e 100644
--- a/src/addons/mod/forum/pages/discussion/discussion.page.ts
+++ b/src/addons/mod/forum/pages/discussion/discussion.page.ts
@@ -107,6 +107,7 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes
     availabilityMessage: string | null = null;
     showQAMessage = false;
     leavingPage = false;
+    externalUrl?: string;
 
     protected forumId?: number;
     protected postId?: number;
@@ -164,6 +165,7 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes
         }
 
         this.isOnline = CoreApp.isOnline();
+        this.externalUrl = CoreSites.getCurrentSite()?.createSiteUrl('/mod/forum/discuss.php', { d: this.discussionId.toString() });
         this.onlineObserver = Network.onChange().subscribe(() => {
             // Execute the callback in the Angular zone, so change detection doesn't stop working.
             NgZone.run(() => {