From f44543a1bb776fa8bdfd3b605d67197d1466eb20 Mon Sep 17 00:00:00 2001
From: Kyle D <kdumontnu@gmail.com>
Date: Thu, 15 Apr 2021 10:53:57 -0600
Subject: [PATCH] Disable Stars config option (#14653)

* Add config option to disable stars

* Replace "stars" with watched in user profile

* Add documentation
---
 custom/conf/app.example.ini                   |  2 ++
 .../doc/advanced/config-cheat-sheet.en-us.md  |  1 +
 models/repo_list.go                           |  6 +++++
 modules/context/context.go                    |  1 +
 modules/setting/repository.go                 |  2 ++
 modules/structs/settings.go                   |  1 +
 options/locale/locale_en-US.ini               |  1 +
 routers/api/v1/settings/settings.go           |  1 +
 routers/user/profile.go                       | 21 ++++++++++++++++
 templates/base/head_navbar.tmpl               | 10 ++++----
 templates/explore/repo_list.tmpl              |  4 +++-
 templates/explore/repo_search.tmpl            |  6 +++--
 templates/repo/header.tmpl                    | 24 ++++++++++---------
 templates/swagger/v1_json.tmpl                |  4 ++++
 templates/user/dashboard/repolist.tmpl        | 10 ++++----
 templates/user/profile.tmpl                   | 16 +++++++++----
 16 files changed, 83 insertions(+), 27 deletions(-)

diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini
index 4fdc4b0214..35f1bfaeab 100644
--- a/custom/conf/app.example.ini
+++ b/custom/conf/app.example.ini
@@ -70,6 +70,8 @@ PREFIX_ARCHIVE_FILES = true
 DISABLE_MIRRORS = false
 ; Disable migrating feature.
 DISABLE_MIGRATIONS = false
+; Disable stars feature.
+DISABLE_STARS = false
 ; The default branch name of new repositories
 DEFAULT_BRANCH = master
 ; Allow adoption of unadopted repositories
diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md
index 9bafee846f..088b56fedd 100644
--- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md
+++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md
@@ -75,6 +75,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
 - `PREFIX_ARCHIVE_FILES`: **true**: Prefix archive files by placing them in a directory named after the repository.
 - `DISABLE_MIRRORS`: **false**: Disable the creation of **new** mirrors. Pre-existing mirrors remain valid.
 - `DISABLE_MIGRATIONS`: **false**: Disable migrating feature.
+- `DISABLE_STARS`: **false**: Disable stars feature.
 - `DEFAULT_BRANCH`: **master**: Default branch name of all repositories.
 - `ALLOW_ADOPTION_OF_UNADOPTED_REPOSITORIES`: **false**: Allow non-admin users to adopt unadopted repositories
 - `ALLOW_DELETION_OF_UNADOPTED_REPOSITORIES`: **false**: Allow non-admin users to delete unadopted repositories
diff --git a/models/repo_list.go b/models/repo_list.go
index 74bc256190..b4a6d9e438 100644
--- a/models/repo_list.go
+++ b/models/repo_list.go
@@ -143,6 +143,7 @@ type SearchRepoOptions struct {
 	OrderBy         SearchOrderBy
 	Private         bool // Include private repositories in results
 	StarredByID     int64
+	WatchedByID     int64
 	AllPublic       bool // Include also all public repositories of users and public organisations
 	AllLimited      bool // Include also all public repositories of limited organisations
 	// None -> include public and private
@@ -241,6 +242,11 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
 		cond = cond.And(builder.In("id", builder.Select("repo_id").From("star").Where(builder.Eq{"uid": opts.StarredByID})))
 	}
 
+	// Restrict to watched repositories
+	if opts.WatchedByID > 0 {
+		cond = cond.And(builder.In("id", builder.Select("repo_id").From("watch").Where(builder.Eq{"user_id": opts.WatchedByID})))
+	}
+
 	// Restrict repositories to those the OwnerID owns or contributes to as per opts.Collaborate
 	if opts.OwnerID > 0 {
 		accessCond := builder.NewCond()
diff --git a/modules/context/context.go b/modules/context/context.go
index b876487d5e..523499aa61 100644
--- a/modules/context/context.go
+++ b/modules/context/context.go
@@ -704,6 +704,7 @@ func Contexter() func(next http.Handler) http.Handler {
 			ctx.Data["EnableSwagger"] = setting.API.EnableSwagger
 			ctx.Data["EnableOpenIDSignIn"] = setting.Service.EnableOpenIDSignIn
 			ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations
+			ctx.Data["DisableStars"] = setting.Repository.DisableStars
 
 			ctx.Data["ManifestData"] = setting.ManifestData
 
diff --git a/modules/setting/repository.go b/modules/setting/repository.go
index 139512bf00..a6fc73651a 100644
--- a/modules/setting/repository.go
+++ b/modules/setting/repository.go
@@ -43,6 +43,7 @@ var (
 		PrefixArchiveFiles                      bool
 		DisableMirrors                          bool
 		DisableMigrations                       bool
+		DisableStars                            bool `ini:"DISABLE_STARS"`
 		DefaultBranch                           string
 		AllowAdoptionOfUnadoptedRepositories    bool
 		AllowDeleteOfUnadoptedRepositories      bool
@@ -154,6 +155,7 @@ var (
 		PrefixArchiveFiles:                      true,
 		DisableMirrors:                          false,
 		DisableMigrations:                       false,
+		DisableStars:                            false,
 		DefaultBranch:                           "master",
 
 		// Repository editor settings
diff --git a/modules/structs/settings.go b/modules/structs/settings.go
index e15c750356..842b12792d 100644
--- a/modules/structs/settings.go
+++ b/modules/structs/settings.go
@@ -9,6 +9,7 @@ type GeneralRepoSettings struct {
 	MirrorsDisabled      bool `json:"mirrors_disabled"`
 	HTTPGitDisabled      bool `json:"http_git_disabled"`
 	MigrationsDisabled   bool `json:"migrations_disabled"`
+	StarsDisabled        bool `json:"stars_disabled"`
 	TimeTrackingDisabled bool `json:"time_tracking_disabled"`
 	LFSDisabled          bool `json:"lfs_disabled"`
 }
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index c2f835a98c..1a8d253749 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -421,6 +421,7 @@ repositories = Repositories
 activity = Public Activity
 followers = Followers
 starred = Starred Repositories
+watched = Watched Repositories
 projects = Projects
 following = Following
 follow = Follow
diff --git a/routers/api/v1/settings/settings.go b/routers/api/v1/settings/settings.go
index cfb059a3ac..e6417e4074 100644
--- a/routers/api/v1/settings/settings.go
+++ b/routers/api/v1/settings/settings.go
@@ -60,6 +60,7 @@ func GetGeneralRepoSettings(ctx *context.APIContext) {
 		MirrorsDisabled:      setting.Repository.DisableMirrors,
 		HTTPGitDisabled:      setting.Repository.DisableHTTPGit,
 		MigrationsDisabled:   setting.Repository.DisableMigrations,
+		StarsDisabled:        setting.Repository.DisableStars,
 		TimeTrackingDisabled: !setting.Service.EnableTimetracking,
 		LFSDisabled:          !setting.LFS.StartServer,
 	})
diff --git a/routers/user/profile.go b/routers/user/profile.go
index 40619aaf0f..c24614b108 100644
--- a/routers/user/profile.go
+++ b/routers/user/profile.go
@@ -238,6 +238,27 @@ func Profile(ctx *context.Context) {
 			ctx.ServerError("GetProjects", err)
 			return
 		}
+	case "watching":
+		repos, count, err = models.SearchRepository(&models.SearchRepoOptions{
+			ListOptions: models.ListOptions{
+				PageSize: setting.UI.User.RepoPagingNum,
+				Page:     page,
+			},
+			Actor:              ctx.User,
+			Keyword:            keyword,
+			OrderBy:            orderBy,
+			Private:            ctx.IsSigned,
+			WatchedByID:        ctxUser.ID,
+			Collaborate:        util.OptionalBoolFalse,
+			TopicOnly:          topicOnly,
+			IncludeDescription: setting.UI.SearchRepoDescription,
+		})
+		if err != nil {
+			ctx.ServerError("SearchRepository", err)
+			return
+		}
+
+		total = int(count)
 	default:
 		repos, count, err = models.SearchRepository(&models.SearchRepoOptions{
 			ListOptions: models.ListOptions{
diff --git a/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl
index ec5c17b6b4..7e476fa4a4 100644
--- a/templates/base/head_navbar.tmpl
+++ b/templates/base/head_navbar.tmpl
@@ -157,10 +157,12 @@
 						{{svg "octicon-person"}}
 						{{.i18n.Tr "your_profile"}}<!-- Your profile -->
 					</a>
-					<a class="item" href="{{AppSubUrl}}/{{.SignedUser.Name}}?tab=stars">
-						{{svg "octicon-star"}}
-						{{.i18n.Tr "your_starred"}}
-					</a>
+					{{if not .DisableStars}}
+						<a class="item" href="{{AppSubUrl}}/{{.SignedUser.Name}}?tab=stars">
+							{{svg "octicon-star"}}
+							{{.i18n.Tr "your_starred"}}
+						</a>
+					{{end}}
 					<a class="{{if .PageIsUserSettings}}active{{end}} item" href="{{AppSubUrl}}/user/settings">
 						{{svg "octicon-tools"}}
 						{{.i18n.Tr "your_settings"}}<!-- Your settings -->
diff --git a/templates/explore/repo_list.tmpl b/templates/explore/repo_list.tmpl
index 91dc3d8bf4..4ff48b6076 100644
--- a/templates/explore/repo_list.tmpl
+++ b/templates/explore/repo_list.tmpl
@@ -42,7 +42,9 @@
 					{{if .PrimaryLanguage }}
 					<span class="text grey df ac mr-3"><i class="color-icon mr-3" style="background-color: {{.PrimaryLanguage.Color}}"></i>{{ .PrimaryLanguage.Language }}</span>
 					{{end}}
-					<span class="text grey df ac mr-3">{{svg "octicon-star" 16 "mr-3"}}{{.NumStars}}</span>
+					{{if not $.DisableStars}}
+						<span class="text grey df ac mr-3">{{svg "octicon-star" 16 "mr-3"}}{{.NumStars}}</span>
+					{{end}}
 					<span class="text grey df ac mr-3">{{svg "octicon-git-branch" 16 "mr-3"}}{{.NumForks}}</span>
 				</div>
 			</div>
diff --git a/templates/explore/repo_search.tmpl b/templates/explore/repo_search.tmpl
index 5e7bed8b31..c1745525a9 100644
--- a/templates/explore/repo_search.tmpl
+++ b/templates/explore/repo_search.tmpl
@@ -12,8 +12,10 @@
 			<a class="{{if eq .SortType "reversealphabetically"}}active{{end}} item" href="{{$.Link}}?sort=reversealphabetically&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.label.filter_sort.reverse_alphabetically"}}</a>
 			<a class="{{if eq .SortType "recentupdate"}}active{{end}} item" href="{{$.Link}}?sort=recentupdate&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.filter_sort.recentupdate"}}</a>
 			<a class="{{if eq .SortType "leastupdate"}}active{{end}} item" href="{{$.Link}}?sort=leastupdate&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.filter_sort.leastupdate"}}</a>
-			<a class="{{if eq .SortType "moststars"}}active{{end}} item" href="{{$.Link}}?sort=moststars&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.filter_sort.moststars"}}</a>
-			<a class="{{if eq .SortType "feweststars"}}active{{end}} item" href="{{$.Link}}?sort=feweststars&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.filter_sort.feweststars"}}</a>
+			{{if not .DisableStars}}
+				<a class="{{if eq .SortType "moststars"}}active{{end}} item" href="{{$.Link}}?sort=moststars&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.filter_sort.moststars"}}</a>
+				<a class="{{if eq .SortType "feweststars"}}active{{end}} item" href="{{$.Link}}?sort=feweststars&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.filter_sort.feweststars"}}</a>
+			{{end}}
 			<a class="{{if eq .SortType "mostforks"}}active{{end}} item" href="{{$.Link}}?sort=mostforks&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.filter_sort.mostforks"}}</a>
 			<a class="{{if eq .SortType "fewestforks"}}active{{end}} item" href="{{$.Link}}?sort=fewestforks&q={{$.Keyword}}&tab={{$.TabName}}">{{.i18n.Tr "repo.issues.filter_sort.fewestforks"}}</a>
 		</div>
diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl
index 188fc87b6a..ebd0333e8c 100644
--- a/templates/repo/header.tmpl
+++ b/templates/repo/header.tmpl
@@ -71,17 +71,19 @@
 							</a>
 						</div>
 					</form>
-					<form method="post" action="{{$.RepoLink}}/action/{{if $.IsStaringRepo}}un{{end}}star?redirect_to={{$.Link}}">
-						{{$.CsrfTokenHtml}}
-						<div class="ui labeled button{{if not $.IsSigned}} poping up{{end}}" tabindex="0"{{if not $.IsSigned}} data-content="{{$.i18n.Tr "repo.star_guest_user" }}" data-position="top center" data-variation="tiny"{{end}}>
-							<button type="submit" class="ui compact small basic button"{{if not $.IsSigned}} disabled{{end}}>
-								{{if $.IsStaringRepo}}{{svg "octicon-star-fill"}}{{$.i18n.Tr "repo.unstar"}}{{else}}{{svg "octicon-star"}}{{$.i18n.Tr "repo.star"}}{{end}}
-							</button>
-							<a class="ui basic label" href="{{.Link}}/stars">
-								{{CountFmt .NumStars}}
-							</a>
-						</div>
-					</form>
+					{{if not $.DisableStars}}
+						<form method="post" action="{{$.RepoLink}}/action/{{if $.IsStaringRepo}}un{{end}}star?redirect_to={{$.Link}}">
+							{{$.CsrfTokenHtml}}
+							<div class="ui labeled button{{if not $.IsSigned}} poping up{{end}}" tabindex="0"{{if not $.IsSigned}} data-content="{{$.i18n.Tr "repo.star_guest_user" }}" data-position="top center" data-variation="tiny"{{end}}>
+								<button type="submit" class="ui compact small basic button"{{if not $.IsSigned}} disabled{{end}}>
+									{{if $.IsStaringRepo}}{{svg "octicon-star-fill"}}{{$.i18n.Tr "repo.unstar"}}{{else}}{{svg "octicon-star"}}{{$.i18n.Tr "repo.star"}}{{end}}
+								</button>
+								<a class="ui basic label" href="{{.Link}}/stars">
+									{{CountFmt .NumStars}}
+								</a>
+							</div>
+						</form>
+					{{end}}
 					{{if and (not .IsEmpty) ($.Permission.CanRead $.UnitTypeCode)}}
 						<div class="ui labeled button{{if not $.CanSignedUserFork}} poping up disabled{{end}}"{{if and (not $.CanSignedUserFork) $.IsSigned}} data-content="{{$.i18n.Tr "repo.fork_from_self"}}" {{else if not $.IsSigned}} data-content="{{$.i18n.Tr "repo.fork_guest_user"}}"{{end}} data-position="top center" data-variation="tiny" tabindex="0">
 							<a class="ui compact small basic button"{{if $.CanSignedUserFork}} href="{{AppSubUrl}}/repo/fork/{{.ID}}"{{end}}>
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index b548f25f4f..5aeed4a30f 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -14151,6 +14151,10 @@
           "type": "boolean",
           "x-go-name": "MirrorsDisabled"
         },
+        "stars_disabled": {
+          "type": "boolean",
+          "x-go-name": "StarsDisabled"
+        },
         "time_tracking_disabled": {
           "type": "boolean",
           "x-go-name": "TimeTrackingDisabled"
diff --git a/templates/user/dashboard/repolist.tmpl b/templates/user/dashboard/repolist.tmpl
index 9dfda9d84e..8ac07e1df6 100644
--- a/templates/user/dashboard/repolist.tmpl
+++ b/templates/user/dashboard/repolist.tmpl
@@ -130,10 +130,12 @@
 									{{svg "octicon-archive" 16 "ml-2"}}
 								</span>
 							</div>
-							<div class="text light grey df ac">
-								${repo.stars_count}
-								{{svg "octicon-star" 16 "ml-2"}}
-							</div>
+							{{if not .DisableStars}}
+								<div class="text light grey df ac">
+									${repo.stars_count}
+									{{svg "octicon-star" 16 "ml-2"}}
+								</div>
+							{{end}}
 						</a>
 					</li>
 				</ul>
diff --git a/templates/user/profile.tmpl b/templates/user/profile.tmpl
index ddad4c46c3..18f3c9f6dd 100644
--- a/templates/user/profile.tmpl
+++ b/templates/user/profile.tmpl
@@ -84,16 +84,22 @@
 			</div>
 			<div class="ui eleven wide column">
 				<div class="ui secondary stackable pointing tight menu">
-					<a class='{{if and (ne .TabName "activity") (ne .TabName "following") (ne .TabName "followers") (ne .TabName "stars") (ne .TabName "projects")}}active{{end}} item' href="{{.Owner.HomeLink}}">
+					<a class='{{if and (ne .TabName "activity") (ne .TabName "following") (ne .TabName "followers") (ne .TabName "stars") (ne .TabName "watching") (ne .TabName "projects")}}active{{end}} item' href="{{.Owner.HomeLink}}">
 						{{svg "octicon-repo"}} {{.i18n.Tr "user.repositories"}}
 					</a>
 					<a class='{{if eq .TabName "activity"}}active{{end}} item' href="{{.Owner.HomeLink}}?tab=activity">
 						{{svg "octicon-rss"}} {{.i18n.Tr "user.activity"}}
 					</a>
-					<a class='{{if eq .TabName "stars"}}active{{end}} item' href="{{.Owner.HomeLink}}?tab=stars">
-						{{svg "octicon-star"}}  {{.i18n.Tr "user.starred"}}
-						<div class="ui label">{{.Owner.NumStars}}</div>
-					</a>
+					{{if not .DisableStars}}
+						<a class='{{if eq .TabName "stars"}}active{{end}} item' href="{{.Owner.HomeLink}}?tab=stars">
+							{{svg "octicon-star"}}  {{.i18n.Tr "user.starred"}}
+							<div class="ui label">{{.Owner.NumStars}}</div>
+						</a>
+					{{else}}
+						<a class='{{if eq .TabName "watching"}}active{{end}} item' href="{{.Owner.HomeLink}}?tab=watching">
+							{{svg "octicon-eye"}}  {{.i18n.Tr "user.watched"}}
+						</a>
+					{{end}}
 					<a class='{{if eq .TabName "following"}}active{{end}} item' href="{{.Owner.HomeLink}}?tab=following">
 						{{svg "octicon-person"}}  {{.i18n.Tr "user.following"}}
 						<div class="ui label">{{.Owner.NumFollowing}}</div>