// Copyright 2015 The Gogs Authors. All rights reserved. // Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package user import ( "errors" "fmt" "net/http" "strings" activities_model "code.gitea.io/gitea/models/activities" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" project_model "code.gitea.io/gitea/models/project" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/routers/web/feed" "code.gitea.io/gitea/routers/web/org" user_service "code.gitea.io/gitea/services/user" ) // Profile render user's profile page func Profile(ctx *context.Context) { if strings.Contains(ctx.Req.Header.Get("Accept"), "application/rss+xml") { feed.ShowUserFeedRSS(ctx) return } if strings.Contains(ctx.Req.Header.Get("Accept"), "application/atom+xml") { feed.ShowUserFeedAtom(ctx) return } if ctx.ContextUser.IsOrganization() { org.Home(ctx) return } // check view permissions if !user_model.IsUserVisibleToViewer(ctx, ctx.ContextUser, ctx.Doer) { ctx.NotFound("user", fmt.Errorf(ctx.ContextUser.Name)) return } // advertise feed via meta tag ctx.Data["FeedURL"] = ctx.ContextUser.HomeLink() // Show OpenID URIs openIDs, err := user_model.GetUserOpenIDs(ctx.ContextUser.ID) if err != nil { ctx.ServerError("GetUserOpenIDs", err) return } var isFollowing bool var isBlocked bool if ctx.Doer != nil { isFollowing = user_model.IsFollowing(ctx.Doer.ID, ctx.ContextUser.ID) isBlocked = user_model.IsBlocked(ctx, ctx.Doer.ID, ctx.ContextUser.ID) } ctx.Data["Title"] = ctx.ContextUser.DisplayName() ctx.Data["PageIsUserProfile"] = true ctx.Data["ContextUser"] = ctx.ContextUser ctx.Data["OpenIDs"] = openIDs ctx.Data["IsFollowing"] = isFollowing ctx.Data["IsBlocked"] = isBlocked if setting.Service.EnableUserHeatmap { data, err := activities_model.GetUserHeatmapDataByUser(ctx.ContextUser, ctx.Doer) if err != nil { ctx.ServerError("GetUserHeatmapDataByUser", err) return } ctx.Data["HeatmapData"] = data ctx.Data["HeatmapTotalContributions"] = activities_model.GetTotalContributionsInHeatmap(data) } if len(ctx.ContextUser.Description) != 0 { content, err := markdown.RenderString(&markup.RenderContext{ URLPrefix: ctx.Repo.RepoLink, Metas: map[string]string{"mode": "document"}, GitRepo: ctx.Repo.GitRepo, Ctx: ctx, }, ctx.ContextUser.Description) if err != nil { ctx.ServerError("RenderString", err) return } ctx.Data["RenderedDescription"] = content } repo, err := repo_model.GetRepositoryByName(ctx.ContextUser.ID, ".profile") if err == nil && !repo.IsEmpty { gitRepo, err := git.OpenRepository(ctx, repo.RepoPath()) if err != nil { ctx.ServerError("OpenRepository", err) return } defer gitRepo.Close() commit, err := gitRepo.GetBranchCommit(repo.DefaultBranch) if err != nil { ctx.ServerError("GetBranchCommit", err) return } blob, err := commit.GetBlobByPath("README.md") if err == nil { bytes, err := blob.GetBlobContent(setting.UI.MaxDisplayFileSize) if err != nil { ctx.ServerError("GetBlobContent", err) return } profileContent, err := markdown.RenderString(&markup.RenderContext{ Ctx: ctx, GitRepo: gitRepo, }, bytes) if err != nil { ctx.ServerError("RenderString", err) return } ctx.Data["ProfileReadme"] = profileContent } } showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID) orgs, err := organization.FindOrgs(organization.FindOrgOptions{ UserID: ctx.ContextUser.ID, IncludePrivate: showPrivate, }) if err != nil { ctx.ServerError("FindOrgs", err) return } ctx.Data["Orgs"] = orgs ctx.Data["HasOrgsVisible"] = organization.HasOrgsVisible(orgs, ctx.Doer) badges, _, err := user_model.GetUserBadges(ctx, ctx.ContextUser) if err != nil { ctx.ServerError("GetUserBadges", err) return } ctx.Data["Badges"] = badges tab := ctx.FormString("tab") ctx.Data["TabName"] = tab page := ctx.FormInt("page") if page <= 0 { page = 1 } pagingNum := setting.UI.User.RepoPagingNum if tab == "activity" { pagingNum = setting.UI.FeedPagingNum } topicOnly := ctx.FormBool("topic") var ( repos []*repo_model.Repository count int64 total int orderBy db.SearchOrderBy ) ctx.Data["SortType"] = ctx.FormString("sort") switch ctx.FormString("sort") { case "newest": orderBy = db.SearchOrderByNewest case "oldest": orderBy = db.SearchOrderByOldest case "recentupdate": orderBy = db.SearchOrderByRecentUpdated case "leastupdate": orderBy = db.SearchOrderByLeastUpdated case "reversealphabetically": orderBy = db.SearchOrderByAlphabeticallyReverse case "alphabetically": orderBy = db.SearchOrderByAlphabetically case "moststars": orderBy = db.SearchOrderByStarsReverse case "feweststars": orderBy = db.SearchOrderByStars case "mostforks": orderBy = db.SearchOrderByForksReverse case "fewestforks": orderBy = db.SearchOrderByForks default: ctx.Data["SortType"] = "recentupdate" orderBy = db.SearchOrderByRecentUpdated } keyword := ctx.FormTrim("q") ctx.Data["Keyword"] = keyword language := ctx.FormTrim("language") ctx.Data["Language"] = language followers, numFollowers, err := user_model.GetUserFollowers(ctx, ctx.ContextUser, ctx.Doer, db.ListOptions{ PageSize: pagingNum, Page: page, }) if err != nil { ctx.ServerError("GetUserFollowers", err) return } ctx.Data["NumFollowers"] = numFollowers following, numFollowing, err := user_model.GetUserFollowing(ctx, ctx.ContextUser, ctx.Doer, db.ListOptions{ PageSize: pagingNum, Page: page, }) if err != nil { ctx.ServerError("GetUserFollowing", err) return } ctx.Data["NumFollowing"] = numFollowing switch tab { case "followers": ctx.Data["Cards"] = followers total = int(count) case "following": ctx.Data["Cards"] = following total = int(count) case "activity": date := ctx.FormString("date") items, count, err := activities_model.GetFeeds(ctx, activities_model.GetFeedsOptions{ RequestedUser: ctx.ContextUser, Actor: ctx.Doer, IncludePrivate: showPrivate, OnlyPerformedBy: true, IncludeDeleted: false, Date: date, ListOptions: db.ListOptions{ PageSize: pagingNum, Page: page, }, }) if err != nil { ctx.ServerError("GetFeeds", err) return } ctx.Data["Feeds"] = items ctx.Data["Date"] = date total = int(count) case "stars": ctx.Data["PageIsProfileStarList"] = true repos, count, err = repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{ ListOptions: db.ListOptions{ PageSize: pagingNum, Page: page, }, Actor: ctx.Doer, Keyword: keyword, OrderBy: orderBy, Private: ctx.IsSigned, StarredByID: ctx.ContextUser.ID, Collaborate: util.OptionalBoolFalse, TopicOnly: topicOnly, Language: language, IncludeDescription: setting.UI.SearchRepoDescription, }) if err != nil { ctx.ServerError("SearchRepository", err) return } total = int(count) case "projects": ctx.Data["OpenProjects"], _, err = project_model.FindProjects(ctx, project_model.SearchOptions{ Page: -1, IsClosed: util.OptionalBoolFalse, Type: project_model.TypeIndividual, }) if err != nil { ctx.ServerError("GetProjects", err) return } case "watching": repos, count, err = repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{ ListOptions: db.ListOptions{ PageSize: pagingNum, Page: page, }, Actor: ctx.Doer, Keyword: keyword, OrderBy: orderBy, Private: ctx.IsSigned, WatchedByID: ctx.ContextUser.ID, Collaborate: util.OptionalBoolFalse, TopicOnly: topicOnly, Language: language, IncludeDescription: setting.UI.SearchRepoDescription, }) if err != nil { ctx.ServerError("SearchRepository", err) return } total = int(count) default: repos, count, err = repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{ ListOptions: db.ListOptions{ PageSize: pagingNum, Page: page, }, Actor: ctx.Doer, Keyword: keyword, OwnerID: ctx.ContextUser.ID, OrderBy: orderBy, Private: ctx.IsSigned, Collaborate: util.OptionalBoolFalse, TopicOnly: topicOnly, Language: language, IncludeDescription: setting.UI.SearchRepoDescription, }) if err != nil { ctx.ServerError("SearchRepository", err) return } total = int(count) } ctx.Data["Repos"] = repos ctx.Data["Total"] = total pager := context.NewPagination(total, pagingNum, page, 5) pager.SetDefaultParams(ctx) pager.AddParam(ctx, "tab", "TabName") if tab != "followers" && tab != "following" && tab != "activity" && tab != "projects" { pager.AddParam(ctx, "language", "Language") } if tab == "activity" { pager.AddParam(ctx, "date", "Date") } ctx.Data["Page"] = pager ctx.Data["IsProjectEnabled"] = true ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled ctx.Data["ShowUserEmail"] = setting.UI.ShowUserEmail && ctx.ContextUser.Email != "" && ctx.IsSigned && !ctx.ContextUser.KeepEmailPrivate ctx.HTML(http.StatusOK, tplProfile) } // Action response for follow/unfollow user request func Action(ctx *context.Context) { var err error var redirectViaJSON bool switch ctx.FormString("action") { case "follow": err = user_model.FollowUser(ctx, ctx.Doer.ID, ctx.ContextUser.ID) case "unfollow": err = user_model.UnfollowUser(ctx, ctx.Doer.ID, ctx.ContextUser.ID) case "block": err = user_service.BlockUser(ctx, ctx.Doer.ID, ctx.ContextUser.ID) redirectViaJSON = true case "unblock": err = user_model.UnblockUser(ctx, ctx.Doer.ID, ctx.ContextUser.ID) } if err != nil { if !errors.Is(err, user_model.ErrBlockedByUser) { ctx.ServerError(fmt.Sprintf("Action (%s)", ctx.FormString("action")), err) return } if ctx.ContextUser.IsOrganization() { ctx.Flash.Error(ctx.Tr("org.follow_blocked_user")) } else { ctx.Flash.Error(ctx.Tr("user.follow_blocked_user")) } } if redirectViaJSON { ctx.JSON(http.StatusOK, map[string]interface{}{ "redirect": ctx.ContextUser.HomeLink(), }) return } // FIXME: We should check this URL and make sure that it's a valid Gitea URL ctx.RedirectToFirst(ctx.FormString("redirect_to"), ctx.ContextUser.HomeLink()) }