diff --git a/pkg/controller/admin/feedback.go b/pkg/controller/admin/feedback.go
index 2767c0a..de96f21 100644
--- a/pkg/controller/admin/feedback.go
+++ b/pkg/controller/admin/feedback.go
@@ -183,17 +183,30 @@ func Feedback() http.HandlerFunc {
}
// Map user IDs.
- var userIDs = []uint64{}
+ var (
+ userIDs = []uint64{}
+ photoIDs = []uint64{}
+ )
for _, p := range page {
if p.UserID > 0 {
userIDs = append(userIDs, p.UserID)
}
+
+ if p.TableName == "photos" && p.TableID > 0 {
+ photoIDs = append(photoIDs, p.TableID)
+ }
}
userMap, err := models.MapUsers(currentUser, userIDs)
if err != nil {
session.FlashError(w, r, "Couldn't map user IDs: %s", err)
}
+ // Map photo IDs.
+ photoMap, err := models.MapPhotos(photoIDs)
+ if err != nil {
+ session.FlashError(w, r, "Couldn't map photo IDs: %s", err)
+ }
+
var vars = map[string]interface{}{
// Filter settings.
"DistinctSubjects": models.DistinctFeedbackSubjects(),
@@ -205,6 +218,7 @@ func Feedback() http.HandlerFunc {
"Acknowledged": acknowledged,
"Feedback": page,
"UserMap": userMap,
+ "PhotoMap": photoMap,
"Pager": pager,
}
if err := tmpl.Execute(w, r, vars); err != nil {
diff --git a/pkg/controller/admin/user_actions.go b/pkg/controller/admin/user_actions.go
index de32497..2200161 100644
--- a/pkg/controller/admin/user_actions.go
+++ b/pkg/controller/admin/user_actions.go
@@ -52,6 +52,7 @@ func MarkPhotoExplicit() http.HandlerFunc {
}
photo.Explicit = true
+ photo.Flagged = true
if err := photo.Save(); err != nil {
session.FlashError(w, r, "Couldn't save photo: %s", err)
} else {
diff --git a/pkg/controller/api/mark_explicit.go b/pkg/controller/api/mark_explicit.go
index ca9f819..b9f0290 100644
--- a/pkg/controller/api/mark_explicit.go
+++ b/pkg/controller/api/mark_explicit.go
@@ -83,6 +83,7 @@ func MarkPhotoExplicit() http.HandlerFunc {
}
photo.Explicit = true
+ photo.Flagged = true
if err := photo.Save(); err != nil {
SendJSON(w, http.StatusBadRequest, Response{
Error: fmt.Sprintf("Couldn't save the photo: %s", err),
@@ -90,6 +91,23 @@ func MarkPhotoExplicit() http.HandlerFunc {
return
}
+ // Send the photo's owner a notification so they are aware.
+ if owner, err := models.GetUser(photo.UserID); err == nil {
+ notif := &models.Notification{
+ UserID: owner.ID,
+ AboutUser: *owner,
+ Type: models.NotificationExplicitPhoto,
+ TableName: "photos",
+ TableID: photo.ID,
+ Link: fmt.Sprintf("/photo/view?id=%d", photo.ID),
+ }
+ if err := models.CreateNotification(notif); err != nil {
+ log.Error("Couldn't create Likes notification: %s", err)
+ }
+ } else {
+ log.Error("MarkExplicit: getting photo owner (%d): %s", photo.UserID, err)
+ }
+
// If a non-admin user has hit this API, log an admin report for visibility and
// to keep a pulse on things (e.g. in case of abuse).
if !currentUser.IsAdmin {
diff --git a/pkg/controller/photo/edit_delete.go b/pkg/controller/photo/edit_delete.go
index 9979fcf..a88bd42 100644
--- a/pkg/controller/photo/edit_delete.go
+++ b/pkg/controller/photo/edit_delete.go
@@ -88,6 +88,9 @@ func Edit() http.HandlerFunc {
// Are we GOING private?
goingPrivate = visibility == models.PhotoPrivate && visibility != photo.Visibility
+
+ // Is the user fighting an 'Explicit' tag added by the community?
+ isFightingExplicitFlag = photo.Flagged && photo.Explicit && !isExplicit
)
if len(altText) > config.AltTextMaxLength {
@@ -141,6 +144,31 @@ func Edit() http.HandlerFunc {
setProfilePic = false
}
+ // If the user is fighting a recent Explicit flag from the community.
+ if isFightingExplicitFlag {
+
+ // Notify the admin (unless we are an admin).
+ if !requestUser.IsAdmin {
+ fb := &models.Feedback{
+ Intent: "report",
+ Subject: "Explicit photo flag dispute",
+ UserID: currentUser.ID,
+ TableName: "photos",
+ TableID: photo.ID,
+ Message: "A user's photo was recently **flagged by the community** as Explicit, and its owner " +
+ "has **removed** the Explicit setting.\n\n" +
+ "Please check out the photo below and verify what its Explicit setting should be:",
+ }
+
+ if err := models.CreateFeedback(fb); err != nil {
+ log.Error("Couldn't save feedback from user updating their DOB: %s", err)
+ }
+ }
+
+ // Allow this change but clear the Flagged status.
+ photo.Flagged = false
+ }
+
if err := photo.Save(); err != nil {
session.FlashError(w, r, "Couldn't save photo: %s", err)
}
diff --git a/pkg/models/notification.go b/pkg/models/notification.go
index 32b73a1..bf5c708 100644
--- a/pkg/models/notification.go
+++ b/pkg/models/notification.go
@@ -46,6 +46,7 @@ const (
NotificationPrivatePhoto NotificationType = "private_photo" // private photo grants
NotificationNewPhoto NotificationType = "new_photo"
NotificationForumModerator NotificationType = "forum_moderator" // appointed as a forum moderator
+ NotificationExplicitPhoto NotificationType = "explicit_photo" // user photo was flagged explicit
NotificationCustom NotificationType = "custom" // custom message pushed
)
diff --git a/pkg/models/notification_filters.go b/pkg/models/notification_filters.go
index a107b8a..9368ab3 100644
--- a/pkg/models/notification_filters.go
+++ b/pkg/models/notification_filters.go
@@ -85,7 +85,13 @@ func (nf NotificationFilter) Query() (where string, placeholders []interface{},
types = append(types, NotificationPrivatePhoto)
}
if nf.Misc {
- types = append(types, NotificationFriendApproved, NotificationCertApproved, NotificationCertRejected, NotificationCustom)
+ types = append(types,
+ NotificationFriendApproved,
+ NotificationCertApproved,
+ NotificationCertRejected,
+ NotificationExplicitPhoto,
+ NotificationCustom,
+ )
}
return "type IN ?", types, true
diff --git a/pkg/models/photo.go b/pkg/models/photo.go
index ddbc818..8e8e64e 100644
--- a/pkg/models/photo.go
+++ b/pkg/models/photo.go
@@ -274,6 +274,54 @@ func GetOrphanedPhotos() ([]*Photo, int64, error) {
return ps, count, res.Error
}
+// PhotoMap helps map a set of users to look up by ID.
+type PhotoMap map[uint64]*Photo
+
+// MapPhotos looks up a set of photos IDs in bulk and returns a PhotoMap suitable for templates.
+func MapPhotos(photoIDs []uint64) (PhotoMap, error) {
+ var (
+ photoMap = PhotoMap{}
+ set = map[uint64]interface{}{}
+ distinct = []uint64{}
+ )
+
+ // Uniqueify the IDs.
+ for _, uid := range photoIDs {
+ if _, ok := set[uid]; ok {
+ continue
+ }
+ set[uid] = nil
+ distinct = append(distinct, uid)
+ }
+
+ var (
+ photos = []*Photo{}
+ result = DB.Model(&Photo{}).Where("id IN ?", distinct).Find(&photos)
+ )
+
+ if result.Error == nil {
+ for _, row := range photos {
+ photoMap[row.ID] = row
+ }
+ }
+
+ return photoMap, result.Error
+}
+
+// Has a photo ID in the map?
+func (pm PhotoMap) Has(id uint64) bool {
+ _, ok := pm[id]
+ return ok
+}
+
+// Get a photo from the PhotoMap.
+func (pm PhotoMap) Get(id uint64) *Photo {
+ if photo, ok := pm[id]; ok {
+ return photo
+ }
+ return nil
+}
+
/*
IsSiteGalleryThrottled returns whether the user is throttled from marking additional pictures for the Site Gallery.
diff --git a/web/static/css/theme.css b/web/static/css/theme.css
index 4d83e54..2c263d9 100644
--- a/web/static/css/theme.css
+++ b/web/static/css/theme.css
@@ -16,6 +16,10 @@ abbr {
cursor: default;
}
+.has-text-smaller {
+ font-size: smaller;
+}
+
img {
/* https://stackoverflow.com/questions/12906789/preventing-an-image-from-being-draggable-or-selectable-without-using-js */
user-drag: none;
diff --git a/web/templates/account/dashboard.html b/web/templates/account/dashboard.html
index 4e0fbc9..fb5a78b 100644
--- a/web/templates/account/dashboard.html
+++ b/web/templates/account/dashboard.html
@@ -600,6 +600,11 @@
You have been appointed as a moderator
for the forum {{$Body.Forum.Title}}!
+ {{else if eq .Type "explicit_photo"}}
+
+
+ Your photo was marked as Explicit!
+
{{else}}
{{.AboutUser.Username}} {{.Type}} {{.TableName}} {{.TableID}}
{{end}}
@@ -628,6 +633,18 @@
See all comments
+ {{else if eq .Type "explicit_photo"}}
+
+
+ A community member thinks that this photo should have been marked as 'Explicit' when
+ it was uploaded.
+
+
+ Please review our Explicit Photos policy
+ and remember to correctly mark your new uploads as 'explicit' when they contain sexually
+ suggestive content.
+
diff --git a/web/templates/faq.html b/web/templates/faq.html
index 52adb6d..8595933 100644
--- a/web/templates/faq.html
+++ b/web/templates/faq.html
@@ -701,6 +701,12 @@
content from other users -- by default this site is "normal nudes" friendly!
+
+ If you disagree that this photo should have been marked as 'Explicit,' you MAY
+ uncheck the box below and remove the Explicit status. Note: the website admin will
+ be notified to take a look as well if you do this, to verify that your photo has the correct 'Explicit'
+ setting.
+
+
+ {{end}}
+
{{if eq .Intent "profile_pic"}}
Your default profile picture should
@@ -405,19 +432,23 @@
that to your page, just not as your default profile picture!
{{else}}
-
-
- Mark this box if this photo contains any explicit content, including an
- erect penis, close-up of genitalia, or any depiction of sexual activity.
- Use your best judgment. "Normal nudes" such as full body nudes in a
- non-sexual context do not need to check this box.
-
+
+
+ Mark this box if this photo contains any explicit content, including an
+ erect penis, close-up of genitalia, or any depiction of sexual activity.
+ Use your best judgment. "Normal nudes" such as full body nudes in a
+ non-sexual context do not need to check this box.
+
- A photo is considered "explicit" if it depicts any of the following features:
+ A photo is considered "explicit" if it depicts any of the following features:
A close-up view of genitalia or where the genitals are the central focus of the picture.
-
An erect penis if the subject has one, especially if they are grabbing it.
+
An erect or semi-erect penis if the subject has one, especially if they are grabbing it.
"Spread eagle" pictures that clearly and especially show intimate body parts such
as butt holes or vulvae.
- A depiction of a sexual act, including but not limited to: masturbation, oral sex,
+ A depiction of any sexual activity, including but not limited to: masturbation, oral sex,
anal or vaginal penetration, humping, or any content intended to sexually arouse the
viewer. If it can be reasonably considered to be "porn" it is an explicit photo.
@@ -405,6 +405,17 @@
+
+ As a general rule of thumb: if a picture could be reasonably considered to be "porn" then you should
+ mark it as Explicit when uploading it to your gallery.
+
+
+
+ Important: extreme and commonly offensive content (such as fisting, gaping or prolapsed
+ ass holes, etc.) are NOT permitted on {{PrettyTitle}}. Please review the following
+ section for a list of Prohibited Content.
+
+
Prohibited Content
@@ -414,6 +425,7 @@
+ Illegal content:
You may NOT upload any content that is considered to be illegal in the United States or
in any of the 50 States therein. This includes, but is not limited to: bestiality (or
sexual acts involving animals), child sexually abusive material (CSAM), ANY nude photo
@@ -423,6 +435,7 @@
or other unlawful content.
+ Extreme content:
You may NOT upload sexual material depicting extreme or commonly offensive content
including, but not limited to: watersports (peeing onto or into another person), scat
(any depiction of obviously apparent fecal matter), prolapsed rectum, anal fisting,