Admin labels on photos surrounding explicit flags

* Add 'admin labels' to photos so an admin can classify a photo as:
  * Not Explicit: e.g. it was flagged by the community but does not
    actually need to be explicit. This option will hide the prompt to
    report the explicit photo again.
  * Force Explicit: if a user is fighting an explicit flag and keeps
    removing it from their photo, the photo can be force marked
    explicit.
* Admin labels appear on the Permalink page and in the edit photo
  settings when viewed as a photo moderator admin.
This commit is contained in:
Noah Petherbridge 2024-10-01 23:13:09 -07:00
parent 542d0bb300
commit 295183559d
7 changed files with 137 additions and 4 deletions

View File

@ -0,0 +1,37 @@
package config
import "strings"
// Admin Labels.
const (
// Admin Labels for Photos
AdminLabelPhotoNonExplicit = "non-explicit"
AdminLabelPhotoForceExplicit = "force-explicit"
)
var (
AdminLabelPhotoOptions = []ChecklistOption{
{
Value: AdminLabelPhotoNonExplicit,
Label: "This is not an Explicit photo",
Help: "Hide the prompt 'Should this photo be marked as explicit?' as this photo does not NEED to be Explicit. " +
"Note: the owner of this photo MAY still mark it explicit if they want to.",
},
{
Value: AdminLabelPhotoForceExplicit,
Label: "Force this photo to be marked as Explicit",
Help: "Enabling this option will force the Explicit tag to stay on, and not allow the user to remove it.",
},
}
)
// HasAdminLabel checks if a comma-separated set of admin labels contains the label.
func HasAdminLabel(needle string, haystack string) bool {
labels := strings.Split(haystack, ",")
for _, label := range labels {
if strings.TrimSpace(label) == needle {
return true
}
}
return false
}

View File

@ -111,6 +111,24 @@ func Edit() http.HandlerFunc {
models.NewFieldDiff("Visibility", photo.Visibility, visibility), models.NewFieldDiff("Visibility", photo.Visibility, visibility),
} }
// Admin label options.
if requestUser.HasAdminScope(config.ScopePhotoModerator) {
var adminLabel string
if labels, ok := r.PostForm["admin_label"]; ok {
adminLabel = strings.Join(labels, ",")
}
diffs = append(diffs,
models.NewFieldDiff("Admin Label", photo.AdminLabel, adminLabel),
)
photo.AdminLabel = adminLabel
}
// Admin label: forced explicit?
if photo.HasAdminLabelForceExplicit() {
isExplicit = true
}
photo.Caption = caption photo.Caption = caption
photo.AltText = altText photo.AltText = altText
photo.Explicit = isExplicit photo.Explicit = isExplicit
@ -167,6 +185,9 @@ func Edit() http.HandlerFunc {
// Allow this change but clear the Flagged status. // Allow this change but clear the Flagged status.
photo.Flagged = false photo.Flagged = false
// Clear the notification about this.
models.RemoveSpecificNotification(currentUser.ID, models.NotificationExplicitPhoto, "photos", photo.ID)
} }
if err := photo.Save(); err != nil { if err := photo.Save(); err != nil {
@ -211,6 +232,10 @@ func Edit() http.HandlerFunc {
"EditPhoto": photo, "EditPhoto": photo,
"SiteGalleryThrottled": SiteGalleryThrottled, "SiteGalleryThrottled": SiteGalleryThrottled,
"SiteGalleryThrottleLimit": config.SiteGalleryRateLimitMax, "SiteGalleryThrottleLimit": config.SiteGalleryRateLimitMax,
// Available admin labels enum.
"RequestUser": requestUser,
"AvailableAdminLabels": config.AdminLabelPhotoOptions,
} }
if err := tmpl.Execute(w, r, vars); err != nil { if err := tmpl.Execute(w, r, vars); err != nil {

View File

@ -22,6 +22,7 @@ type Photo struct {
Caption string Caption string
AltText string AltText string
Flagged bool // photo has been reported by the community Flagged bool // photo has been reported by the community
AdminLabel string // admin label(s) on this photo (e.g. verified non-explicit)
Visibility PhotoVisibility `gorm:"index"` Visibility PhotoVisibility `gorm:"index"`
Gallery bool `gorm:"index"` // photo appears in the public gallery (if public) Gallery bool `gorm:"index"` // photo appears in the public gallery (if public)
Explicit bool `gorm:"index"` // is an explicit photo Explicit bool `gorm:"index"` // is an explicit photo
@ -274,6 +275,21 @@ func GetOrphanedPhotos() ([]*Photo, int64, error) {
return ps, count, res.Error return ps, count, res.Error
} }
// HasAdminLabelNonExplicit checks if the non-explicit admin label is applied to this photo.
func (p *Photo) HasAdminLabelNonExplicit() bool {
return config.HasAdminLabel(config.AdminLabelPhotoNonExplicit, p.AdminLabel)
}
// HasAdminLabelForceExplicit checks if the force-explicit admin label is applied to this photo.
func (p *Photo) HasAdminLabelForceExplicit() bool {
return config.HasAdminLabel(config.AdminLabelPhotoForceExplicit, p.AdminLabel)
}
// HasAdminLabel checks if the photo has an admin label (for convenient front-end access on the Edit page).
func (p *Photo) HasAdminLabel(label string) bool {
return config.HasAdminLabel(label, p.AdminLabel)
}
// PhotoMap helps map a set of users to look up by ID. // PhotoMap helps map a set of users to look up by ID.
type PhotoMap map[uint64]*Photo type PhotoMap map[uint64]*Photo

View File

@ -239,6 +239,11 @@
class="blurred-explicit"> class="blurred-explicit">
</a> </a>
</div> </div>
<div class="mt-3 has-text-smaller">
<a href="/photo/edit?id={{.TableID}}">
<i class="fa fa-edit mr-1"></i> Edit this photo
</a>
</div>
{{end}} {{end}}
{{end}} {{end}}
</div> </div>

View File

@ -568,7 +568,7 @@
{{template "card-body" .}} {{template "card-body" .}}
<!-- Quick mark photo as explicit --> <!-- Quick mark photo as explicit -->
{{if and (not .Explicit) (ne .UserID $Root.CurrentUser.ID)}} {{if and (not .Explicit) (ne .UserID $Root.CurrentUser.ID) (not .HasAdminLabelNonExplicit)}}
<div class="mt-2"> <div class="mt-2">
<a href="#" <a href="#"
class="has-text-danger is-size-7 nonshy-mark-explicit" class="has-text-danger is-size-7 nonshy-mark-explicit"
@ -694,7 +694,7 @@
{{template "card-body" .}} {{template "card-body" .}}
<!-- Quick mark photo as explicit --> <!-- Quick mark photo as explicit -->
{{if and (not .Explicit) (ne .UserID $Root.CurrentUser.ID)}} {{if and (not .Explicit) (ne .UserID $Root.CurrentUser.ID) (not .HasAdminLabelNonExplicit)}}
<div class="mt-2"> <div class="mt-2">
<a href="#" <a href="#"
class="has-text-danger is-size-7 nonshy-mark-explicit" class="has-text-danger is-size-7 nonshy-mark-explicit"

View File

@ -109,8 +109,18 @@
<small class="has-text-grey">Uploaded {{.Photo.CreatedAt.Format "Jan _2 2006 15:04:05"}}</small> <small class="has-text-grey">Uploaded {{.Photo.CreatedAt.Format "Jan _2 2006 15:04:05"}}</small>
</div> </div>
<!-- Show admin labels to admins -->
{{if .CurrentUser.HasAdminScope "social.moderator.photo"}}
{{if .Photo.AdminLabel}}
<div class="mt-2">
<i class="fa fa-peace has-text-danger mr-1"></i>
Admin Label: {{.Photo.AdminLabel}}
</div>
{{end}}
{{end}}
<!-- Quick mark photo as explicit --> <!-- Quick mark photo as explicit -->
{{if and (not .Photo.Explicit) (ne .Photo.UserID .CurrentUser.ID)}} {{if and (not .Photo.Explicit) (ne .Photo.UserID .CurrentUser.ID) (not .Photo.HasAdminLabelNonExplicit)}}
<div class="mt-1"> <div class="mt-1">
<a href="#" <a href="#"
class="has-text-danger is-size-7 nonshy-mark-explicit" class="has-text-danger is-size-7 nonshy-mark-explicit"

View File

@ -17,6 +17,7 @@
</div> </div>
</section> </section>
{{ $Root := . }}
{{ $User := .CurrentUser }} {{ $User := .CurrentUser }}
<!-- Drag/Drop Modal --> <!-- Drag/Drop Modal -->
@ -438,7 +439,8 @@
<input type="checkbox" <input type="checkbox"
name="explicit" name="explicit"
value="true" value="true"
{{if .EditPhoto.Explicit}}checked{{end}}> {{if .EditPhoto.Explicit}}checked{{end}}
{{if .EditPhoto.HasAdminLabelForceExplicit}}disabled{{end}}>
{{if $IsFlagged}}<del class="cursor-not-allowed">{{end}} {{if $IsFlagged}}<del class="cursor-not-allowed">{{end}}
This photo contains explicit content This photo contains explicit content
{{if $IsFlagged}}</del>{{end}} {{if $IsFlagged}}</del>{{end}}
@ -488,6 +490,44 @@
</button> </button>
</div> </div>
<!-- Admin Labels -->
{{if and .EditPhoto (.RequestUser.HasAdminScope "social.moderator.photo")}}
<hr>
<div class="field">
<label class="label has-text-danger">
<span>Admin Labels</span>
<span class="icon"><i class="fa fa-peace"></i></span>
</label>
<p class="help">
The options below can apply moderation rules to this picture, especially regarding
its 'explicit' status. For example: if a community member flagged this picture as
explicit, but it does NOT need to be marked as such, select that label below: and
the website will no longer allow this photo to be flagged as explicit again.
</p>
</div>
{{range .AvailableAdminLabels}}
<div class="field">
<label class="checkbox">
<input type="checkbox"
name="admin_label"
value="{{.Value}}"
{{if ($Root.EditPhoto.HasAdminLabel .Value)}}checked{{end}}>
{{.Label}}
</label>
<p class="help">
{{.Help}}
</p>
</div>
{{end}}
<p class="help">
<strong>Reminder:</strong> click on 'Save Changes' to apply these labels!
</p>
{{end}}
</div> </div>
</div> </div>