jojo/services/context/org.go
Thomas Teixeira 731334e973 fix(web): org projects assignment in issue view (#7999)
Allows user to assign organization projects to their new issues, using the project sidebar selector, even when repository's projects are disabled.
Moreover, the project sidebar selector is now hidden if no projects (repository-wide + organization-wide) are available.

Fixes forgejo/forgejo#5666

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7999
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
2026-05-02 01:29:40 +02:00

293 lines
7.5 KiB
Go

// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2020 The Gitea Authors. All rights reserved.
// Copyright 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package context
import (
"strings"
"forgejo.org/models/organization"
"forgejo.org/models/perm"
"forgejo.org/models/unit"
user_model "forgejo.org/models/user"
"forgejo.org/modules/markup"
"forgejo.org/modules/markup/markdown"
"forgejo.org/modules/setting"
"forgejo.org/modules/structs"
redirect_service "forgejo.org/services/redirect"
)
// Organization contains organization context
type Organization struct {
IsOwner bool
IsMember bool
IsTeamMember bool // Is member of team.
IsTeamAdmin bool // In owner team or team that has admin permission level.
Organization *organization.Organization
OrgLink string
CanCreateOrgRepo bool
Team *organization.Team
Teams []*organization.Team
}
func (org *Organization) CanWriteUnit(ctx *Context, unitType unit.Type) bool {
return org.Organization.UnitPermission(ctx, ctx.Doer, unitType) >= perm.AccessModeWrite
}
func (org *Organization) CanReadUnit(ctx *Context, unitType unit.Type) bool {
return org.Organization.UnitPermission(ctx, ctx.Doer, unitType) >= perm.AccessModeRead
}
func GetOrganizationByParams(ctx *Context) {
orgName := ctx.Params(":org")
var err error
ctx.Org.Organization, err = organization.GetOrgByName(ctx, orgName)
if err != nil {
if organization.IsErrOrgNotExist(err) {
redirectUserID, err := redirect_service.LookupUserRedirect(ctx, ctx.Doer, orgName)
if err == nil {
RedirectToUser(ctx.Base, orgName, redirectUserID)
} else if user_model.IsErrUserRedirectNotExist(err) {
ctx.NotFound("GetUserByName", err)
} else {
ctx.ServerError("LookupRedirect", err)
}
} else {
ctx.ServerError("GetUserByName", err)
}
return
}
}
func ensureIsOrg(ctx *Context) bool {
switch {
// Getting Organization from params
case ctx.ContextUser == nil:
if ctx.Org.Organization == nil {
GetOrganizationByParams(ctx)
}
return !ctx.Written()
// Getting Organization from ContextUser
case ctx.ContextUser.IsOrganization():
if ctx.Org == nil {
ctx.Org = &Organization{}
}
ctx.Org.Organization = (*organization.Organization)(ctx.ContextUser)
return true
}
// ContextUser is an individual User
return false
}
func EnsureOrg() func(*Context) {
return func(ctx *Context) {
ensureIsOrg(ctx)
}
}
// HandleOrgAssignment handles organization assignment
func HandleOrgAssignment(ctx *Context, args ...bool) {
var (
requireMember bool
requireOwner bool
requireTeamMember bool
requireTeamAdmin bool
)
if len(args) >= 1 {
requireMember = args[0]
}
if len(args) >= 2 {
requireOwner = args[1]
}
if len(args) >= 3 {
requireTeamMember = args[2]
}
if len(args) >= 4 {
requireTeamAdmin = args[3]
}
var err error
if !ensureIsOrg(ctx) {
return
}
org := ctx.Org.Organization
// Handle Visibility
if org.Visibility != structs.VisibleTypePublic && !ctx.IsSigned {
// We must be signed in to see limited or private organizations
ctx.NotFound("OrgAssignment", err)
return
}
if org.Visibility == structs.VisibleTypePrivate {
requireMember = true
} else if ctx.IsSigned && ctx.Doer.IsRestricted {
requireMember = true
}
ctx.ContextUser = org.AsUser()
ctx.Data["Org"] = org
// Admin has super access.
if ctx.IsSigned && ctx.Doer.IsAdmin {
ctx.Org.IsOwner = true
ctx.Org.IsMember = true
ctx.Org.IsTeamMember = true
ctx.Org.IsTeamAdmin = true
ctx.Org.CanCreateOrgRepo = true
} else if ctx.IsSigned {
ctx.Org.IsOwner, err = org.IsOwnedBy(ctx, ctx.Doer.ID)
if err != nil {
ctx.ServerError("IsOwnedBy", err)
return
}
if ctx.Org.IsOwner {
ctx.Org.IsMember = true
ctx.Org.IsTeamMember = true
ctx.Org.IsTeamAdmin = true
ctx.Org.CanCreateOrgRepo = true
} else {
ctx.Org.IsMember, err = org.IsOrgMember(ctx, ctx.Doer.ID)
if err != nil {
ctx.ServerError("IsOrgMember", err)
return
}
ctx.Org.CanCreateOrgRepo, err = org.CanCreateOrgRepo(ctx, ctx.Doer.ID)
if err != nil {
ctx.ServerError("CanCreateOrgRepo", err)
return
}
}
} else {
// Fake data.
ctx.Data["SignedUser"] = &user_model.User{}
}
if (requireMember && !ctx.Org.IsMember) ||
(requireOwner && !ctx.Org.IsOwner) {
ctx.NotFound("OrgAssignment", err)
return
}
ctx.Data["IsOrganizationOwner"] = ctx.Org.IsOwner
ctx.Data["IsOrganizationMember"] = ctx.Org.IsMember
ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
ctx.Data["IsModerationEnabled"] = setting.Moderation.Enabled
ctx.Data["IsPublicMember"] = func(uid int64) bool {
is, _ := organization.IsPublicMembership(ctx, ctx.Org.Organization.ID, uid)
return is
}
ctx.Data["CanCreateOrgRepo"] = ctx.Org.CanCreateOrgRepo
ctx.Org.OrgLink = org.AsUser().OrganisationLink()
ctx.Data["OrgLink"] = ctx.Org.OrgLink
// Member
opts := &organization.FindOrgMembersOpts{
Doer: ctx.Doer,
OrgID: org.ID,
IsDoerMember: ctx.Org.IsMember,
}
ctx.Data["NumMembers"], err = organization.CountOrgMembers(ctx, opts)
if err != nil {
ctx.ServerError("CountOrgMembers", err)
return
}
// Team.
if ctx.Org.IsMember {
shouldSeeAllTeams := false
if ctx.Org.IsOwner {
shouldSeeAllTeams = true
} else {
teams, err := org.GetUserTeams(ctx, ctx.Doer.ID)
if err != nil {
ctx.ServerError("GetUserTeams", err)
return
}
for _, team := range teams {
if team.IncludesAllRepositories && team.AccessMode >= perm.AccessModeAdmin {
shouldSeeAllTeams = true
break
}
}
}
if shouldSeeAllTeams {
ctx.Org.Teams, err = org.LoadTeams(ctx)
if err != nil {
ctx.ServerError("LoadTeams", err)
return
}
} else {
ctx.Org.Teams, err = org.GetUserTeams(ctx, ctx.Doer.ID)
if err != nil {
ctx.ServerError("GetUserTeams", err)
return
}
}
ctx.Data["NumTeams"] = len(ctx.Org.Teams)
}
teamName := ctx.Params(":team")
if len(teamName) > 0 {
teamExists := false
for _, team := range ctx.Org.Teams {
if team.LowerName == strings.ToLower(teamName) {
teamExists = true
ctx.Org.Team = team
ctx.Org.IsTeamMember = true
ctx.Data["Team"] = ctx.Org.Team
break
}
}
if !teamExists {
ctx.NotFound("OrgAssignment", err)
return
}
ctx.Data["IsTeamMember"] = ctx.Org.IsTeamMember
if requireTeamMember && !ctx.Org.IsTeamMember {
ctx.NotFound("OrgAssignment", err)
return
}
ctx.Org.IsTeamAdmin = ctx.Org.Team.IsOwnerTeam() || ctx.Org.Team.AccessMode >= perm.AccessModeAdmin
ctx.Data["IsTeamAdmin"] = ctx.Org.IsTeamAdmin
if requireTeamAdmin && !ctx.Org.IsTeamAdmin {
ctx.NotFound("OrgAssignment", err)
return
}
}
ctx.Data["ContextUser"] = ctx.ContextUser
ctx.Data["CanReadProjects"] = ctx.Org.CanReadUnit(ctx, unit.TypeProjects)
ctx.Data["CanReadPackages"] = ctx.Org.CanReadUnit(ctx, unit.TypePackages)
ctx.Data["CanReadCode"] = ctx.Org.CanReadUnit(ctx, unit.TypeCode)
ctx.Data["IsFollowing"] = ctx.Doer != nil && user_model.IsFollowing(ctx, ctx.Doer.ID, ctx.ContextUser.ID)
if len(ctx.ContextUser.Description) != 0 {
content, err := markdown.RenderString(&markup.RenderContext{
Metas: map[string]string{"mode": "document"},
Ctx: ctx,
}, ctx.ContextUser.Description)
if err != nil {
ctx.ServerError("RenderString", err)
return
}
ctx.Data["RenderedDescription"] = content
}
}
// OrgAssignment returns a middleware to handle organization assignment
func OrgAssignment(args ...bool) func(ctx *Context) {
return func(ctx *Context) {
HandleOrgAssignment(ctx, args...)
}
}