Merge branch 'release-0.7.6' 0.7.6
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Wed, 7 Dec 2011 18:29:00 +0000 (19:29 +0100)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Wed, 7 Dec 2011 18:29:00 +0000 (19:29 +0100)
21 files changed:
pom.xml
src/main/java/net/pterodactylus/sone/core/Core.java
src/main/java/net/pterodactylus/sone/core/Options.java
src/main/java/net/pterodactylus/sone/core/SoneDownloader.java
src/main/java/net/pterodactylus/sone/data/Profile.java
src/main/java/net/pterodactylus/sone/data/Sone.java
src/main/java/net/pterodactylus/sone/main/SonePlugin.java
src/main/java/net/pterodactylus/sone/template/ImageLinkFilter.java
src/main/java/net/pterodactylus/sone/template/ProfileAccessor.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/web/EditProfilePage.java
src/main/java/net/pterodactylus/sone/web/OptionsPage.java
src/main/java/net/pterodactylus/sone/web/WebInterface.java
src/main/resources/i18n/sone.en.properties
src/main/resources/static/css/sone.css
src/main/resources/templates/editProfile.html
src/main/resources/templates/include/head.html
src/main/resources/templates/include/soneMenu.html
src/main/resources/templates/include/viewPost.html
src/main/resources/templates/include/viewReply.html
src/main/resources/templates/insert/sone.xml
src/main/resources/templates/options.html

diff --git a/pom.xml b/pom.xml
index 96a9494..279e6c7 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -2,7 +2,7 @@
        <modelVersion>4.0.0</modelVersion>
        <groupId>net.pterodactylus</groupId>
        <artifactId>sone</artifactId>
-       <version>0.7.5</version>
+       <version>0.7.6</version>
        <dependencies>
                <dependency>
                        <groupId>net.pterodactylus</groupId>
index 81c5185..a51a189 100644 (file)
@@ -42,6 +42,7 @@ import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.data.Profile;
 import net.pterodactylus.sone.data.Reply;
 import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.data.Sone.ShowCustomAvatars;
 import net.pterodactylus.sone.data.TemporaryImage;
 import net.pterodactylus.sone.data.Profile.Field;
 import net.pterodactylus.sone.fcp.FcpInterface;
@@ -998,6 +999,8 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                sone.getOptions().addBooleanOption("ShowNotification/NewSones", new DefaultOption<Boolean>(true));
                sone.getOptions().addBooleanOption("ShowNotification/NewPosts", new DefaultOption<Boolean>(true));
                sone.getOptions().addBooleanOption("ShowNotification/NewReplies", new DefaultOption<Boolean>(true));
+               sone.getOptions().addEnumOption("ShowCustomAvatars", new DefaultOption<ShowCustomAvatars>(ShowCustomAvatars.NEVER));
+
                followSone(sone, getSone("nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI"));
                touchConfiguration();
                return sone;
@@ -1431,6 +1434,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                sone.getOptions().addBooleanOption("ShowNotification/NewSones", new DefaultOption<Boolean>(true));
                sone.getOptions().addBooleanOption("ShowNotification/NewPosts", new DefaultOption<Boolean>(true));
                sone.getOptions().addBooleanOption("ShowNotification/NewReplies", new DefaultOption<Boolean>(true));
+               sone.getOptions().addEnumOption("ShowCustomAvatars", new DefaultOption<ShowCustomAvatars>(ShowCustomAvatars.NEVER));
 
                /* load Sone. */
                String sonePrefix = "Sone/" + sone.getId();
@@ -1442,7 +1446,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                String lastInsertFingerprint = configuration.getStringValue(sonePrefix + "/LastInsertFingerprint").getValue("");
 
                /* load profile. */
-               Profile profile = new Profile();
+               Profile profile = new Profile(sone);
                profile.setFirstName(configuration.getStringValue(sonePrefix + "/Profile/FirstName").getValue(null));
                profile.setMiddleName(configuration.getStringValue(sonePrefix + "/Profile/MiddleName").getValue(null));
                profile.setLastName(configuration.getStringValue(sonePrefix + "/Profile/LastName").getValue(null));
@@ -1590,12 +1594,19 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                        album.addImage(image);
                }
 
+               /* load avatar. */
+               String avatarId = configuration.getStringValue(sonePrefix + "/Profile/Avatar").getValue(null);
+               if (avatarId != null) {
+                       profile.setAvatar(getImage(avatarId, false));
+               }
+
                /* load options. */
                sone.getOptions().getBooleanOption("AutoFollow").set(configuration.getBooleanValue(sonePrefix + "/Options/AutoFollow").getValue(null));
                sone.getOptions().getBooleanOption("EnableSoneInsertNotifications").set(configuration.getBooleanValue(sonePrefix + "/Options/EnableSoneInsertNotifications").getValue(null));
                sone.getOptions().getBooleanOption("ShowNotification/NewSones").set(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewSones").getValue(null));
                sone.getOptions().getBooleanOption("ShowNotification/NewPosts").set(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewPosts").getValue(null));
                sone.getOptions().getBooleanOption("ShowNotification/NewReplies").set(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewReplies").getValue(null));
+               sone.getOptions().<ShowCustomAvatars> getEnumOption("ShowCustomAvatars").set(ShowCustomAvatars.valueOf(configuration.getStringValue(sonePrefix + "/Options/ShowCustomAvatars").getValue(ShowCustomAvatars.NEVER.name())));
 
                /* if we’re still here, Sone was loaded successfully. */
                synchronized (sone) {
@@ -2135,6 +2146,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                        configuration.getIntValue(sonePrefix + "/Profile/BirthDay").setValue(profile.getBirthDay());
                        configuration.getIntValue(sonePrefix + "/Profile/BirthMonth").setValue(profile.getBirthMonth());
                        configuration.getIntValue(sonePrefix + "/Profile/BirthYear").setValue(profile.getBirthYear());
+                       configuration.getStringValue(sonePrefix + "/Profile/Avatar").setValue(profile.getAvatar());
 
                        /* save profile fields. */
                        int fieldCounter = 0;
@@ -2228,6 +2240,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                        configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewPosts").setValue(sone.getOptions().getBooleanOption("ShowNotification/NewPosts").getReal());
                        configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewReplies").setValue(sone.getOptions().getBooleanOption("ShowNotification/NewReplies").getReal());
                        configuration.getBooleanValue(sonePrefix + "/Options/EnableSoneInsertNotifications").setValue(sone.getOptions().getBooleanOption("EnableSoneInsertNotifications").getReal());
+                       configuration.getStringValue(sonePrefix + "/Options/ShowCustomAvatars").setValue(sone.getOptions().<ShowCustomAvatars> getEnumOption("ShowCustomAvatars").get().name());
 
                        configuration.save();
 
index 1bdf21f..f280aae 100644 (file)
@@ -226,6 +226,9 @@ public class Options {
        /** Holds all {@link String} {@link Option}s. */
        private final Map<String, Option<String>> stringOptions = Collections.synchronizedMap(new HashMap<String, Option<String>>());
 
+       /** Holds all {@link Enum} {@link Option}s. */
+       private final Map<String, Option<? extends Enum<?>>> enumOptions = Collections.synchronizedMap(new HashMap<String, Option<? extends Enum<?>>>());
+
        /**
         * Adds a boolean option.
         *
@@ -304,4 +307,37 @@ public class Options {
                return stringOptions.get(name);
        }
 
+       /**
+        * Adds an {@link Enum} {@link Option}.
+        *
+        * @param name
+        *            The name of the option
+        * @param enumOption
+        *            The option
+        * @return The given option
+        */
+       public <T extends Enum<T>> Option<T> addEnumOption(String name, Option<T> enumOption) {
+               enumOptions.put(name, enumOption);
+               return enumOption;
+       }
+
+       /**
+        * Returns a {@link Enum} {@link Option}. As the type can probably not be
+        * interred correctly you could help the compiler by calling this method
+        * like this:
+        * <p>
+        *
+        * <pre>
+        * options.&lt;SomeEnum&gt; getEnumOption(&quot;SomeEnumOption&quot;).get();
+        * </pre>
+        *
+        * @param name
+        *            The name of the option
+        * @return The enum option, or {@code null} if there is no enum option with
+        *         the given name
+        */
+       public <T extends Enum<T>> Option<T> getEnumOption(String name) {
+               return (Option<T>) enumOptions.get(name);
+       }
+
 }
index b085ade..5024ab9 100644 (file)
@@ -330,8 +330,10 @@ public class SoneDownloader extends AbstractService {
                Integer profileBirthDay = Numbers.safeParseInteger(profileXml.getValue("birth-day", null));
                Integer profileBirthMonth = Numbers.safeParseInteger(profileXml.getValue("birth-month", null));
                Integer profileBirthYear = Numbers.safeParseInteger(profileXml.getValue("birth-year", null));
-               Profile profile = new Profile().setFirstName(profileFirstName).setMiddleName(profileMiddleName).setLastName(profileLastName);
+               Profile profile = new Profile(sone).setFirstName(profileFirstName).setMiddleName(profileMiddleName).setLastName(profileLastName);
                profile.setBirthDay(profileBirthDay).setBirthMonth(profileBirthMonth).setBirthYear(profileBirthYear);
+               /* avatar is processed after images are loaded. */
+               String avatarId = profileXml.getValue("avatar", null);
 
                /* parse profile fields. */
                SimpleXML profileFieldsXml = profileXml.getNode("fields");
@@ -495,6 +497,11 @@ public class SoneDownloader extends AbstractService {
                        }
                }
 
+               /* process avatar. */
+               if (avatarId != null) {
+                       profile.setAvatar(core.getImage(avatarId, false));
+               }
+
                /* okay, apparently everything was parsed correctly. Now import. */
                /* atomic setter operation on the Sone. */
                synchronized (sone) {
index 6b44d10..6c0b435 100644 (file)
@@ -32,6 +32,9 @@ import net.pterodactylus.util.validation.Validation;
  */
 public class Profile implements Fingerprintable {
 
+       /** The Sone this profile belongs to. */
+       private final Sone sone;
+
        /** The first name. */
        private volatile String firstName;
 
@@ -50,14 +53,20 @@ public class Profile implements Fingerprintable {
        /** The year of the birth date. */
        private volatile Integer birthYear;
 
+       /** The ID of the avatar image. */
+       private volatile String avatar;
+
        /** Additional fields in the profile. */
        private final List<Field> fields = Collections.synchronizedList(new ArrayList<Field>());
 
        /**
         * Creates a new empty profile.
+        *
+        * @param sone
+        *            The Sone this profile belongs to
         */
-       public Profile() {
-               /* do nothing. */
+       public Profile(Sone sone) {
+               this.sone = sone;
        }
 
        /**
@@ -67,15 +76,14 @@ public class Profile implements Fingerprintable {
         *            The profile to copy
         */
        public Profile(Profile profile) {
-               if (profile == null) {
-                       return;
-               }
+               this.sone = profile.sone;
                this.firstName = profile.firstName;
                this.middleName = profile.middleName;
                this.lastName = profile.lastName;
                this.birthDay = profile.birthDay;
                this.birthMonth = profile.birthMonth;
                this.birthYear = profile.birthYear;
+               this.avatar = profile.avatar;
                this.fields.addAll(profile.fields);
        }
 
@@ -84,6 +92,15 @@ public class Profile implements Fingerprintable {
        //
 
        /**
+        * Returns the Sone this profile belongs to.
+        *
+        * @return The Sone this profile belongs to
+        */
+       public Sone getSone() {
+               return sone;
+       }
+
+       /**
         * Returns the first name.
         *
         * @return The first name
@@ -198,6 +215,34 @@ public class Profile implements Fingerprintable {
        }
 
        /**
+        * Returns the ID of the currently selected avatar image.
+        *
+        * @return The ID of the currently selected avatar image, or {@code null} if
+        *         no avatar is selected.
+        */
+       public String getAvatar() {
+               return avatar;
+       }
+
+       /**
+        * Sets the avatar image.
+        *
+        * @param avatar
+        *            The new avatar image, or {@code null} to not select an avatar
+        *            image.
+        * @return This Sone
+        */
+       public Profile setAvatar(Image avatar) {
+               if (avatar == null) {
+                       this.avatar = null;
+                       return this;
+               }
+               Validation.begin().isEqual("Image Owner", avatar.getSone(), sone).check();
+               this.avatar = avatar.getId();
+               return this;
+       }
+
+       /**
         * Sets the year of the birth date.
         *
         * @param birthYear
@@ -367,6 +412,9 @@ public class Profile implements Fingerprintable {
                if (birthYear != null) {
                        fingerprint.append("BirthYear(").append(birthYear).append(')');
                }
+               if (avatar != null) {
+                       fingerprint.append("Avatar(").append(avatar).append(')');
+               }
                fingerprint.append("ContactInformation(");
                for (Field field : fields) {
                        fingerprint.append(field.getName()).append('(').append(field.getValue()).append(')');
index 8b2ec2a..901d676 100644 (file)
@@ -47,6 +47,30 @@ import freenet.keys.FreenetURI;
  */
 public class Sone implements Fingerprintable, Comparable<Sone> {
 
+       /**
+        * The possible values for the “show custom avatars” option.
+        *
+        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+        */
+       public static enum ShowCustomAvatars {
+
+               /** Never show custom avatars. */
+               NEVER,
+
+               /** Only show custom avatars of followed Sones. */
+               FOLLOWED,
+
+               /** Only show custom avatars of Sones you manually trust. */
+               MANUALLY_TRUSTED,
+
+               /** Only show custom avatars of automatically trusted Sones. */
+               TRUSTED,
+
+               /** Always show custom avatars. */
+               ALWAYS,
+
+       }
+
        /** comparator that sorts Sones by their nice name. */
        public static final Comparator<Sone> NICE_NAME_COMPARATOR = new Comparator<Sone>() {
 
@@ -147,7 +171,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
        private volatile long time;
 
        /** The profile of this Sone. */
-       private volatile Profile profile = new Profile();
+       private volatile Profile profile = new Profile(this);
 
        /** The client used by the Sone. */
        private volatile Client client;
index 6a2c1ca..af52a7f 100644 (file)
@@ -83,7 +83,7 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr
        }
 
        /** The version. */
-       public static final Version VERSION = new Version(0, 7, 5);
+       public static final Version VERSION = new Version(0, 7, 6);
 
        /** The logger. */
        private static final Logger logger = Logging.getLogger(SonePlugin.class);
index d7b5eea..1684be7 100644 (file)
@@ -21,6 +21,7 @@ import java.io.StringReader;
 import java.io.StringWriter;
 import java.util.Map;
 
+import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.data.Image;
 import net.pterodactylus.util.number.Numbers;
 import net.pterodactylus.util.object.Default;
@@ -41,16 +42,22 @@ public class ImageLinkFilter implements Filter {
        /** The template to render for the &lt;img&gt; tag. */
        private static final Template linkTemplate = TemplateParser.parse(new StringReader("<img<%ifnull !class> class=\"<%class|css>\"<%/if> src=\"<%src|html><%if forceDownload>?forcedownload=true<%/if>\" alt=\"<%alt|html>\" title=\"<%title|html>\" width=\"<%width|html>\" height=\"<%height|html>\" style=\"position: relative;<%ifnull ! top>top: <% top|html>;<%/if><%ifnull ! left>left: <% left|html>;<%/if>\"/>"));
 
+       /** The core. */
+       private final Core core;
+
        /** The template context factory. */
        private final TemplateContextFactory templateContextFactory;
 
        /**
         * Creates a new image link filter.
         *
+        * @param core
+        *            The core
         * @param templateContextFactory
         *            The template context factory
         */
-       public ImageLinkFilter(TemplateContextFactory templateContextFactory) {
+       public ImageLinkFilter(Core core, TemplateContextFactory templateContextFactory) {
+               this.core = core;
                this.templateContextFactory = templateContextFactory;
        }
 
@@ -59,7 +66,14 @@ public class ImageLinkFilter implements Filter {
         */
        @Override
        public Object format(TemplateContext templateContext, Object data, Map<String, String> parameters) {
-               Image image = (Image) data;
+               Image image = null;
+               if (data instanceof String) {
+                       image = core.getImage((String) data, false);
+               } else if (data instanceof Image) {
+                       image = (Image) data;
+               } else {
+                       return null;
+               }
                String imageClass = parameters.get("class");
                int maxWidth = Numbers.safeParseInteger(parameters.get("max-width"), Integer.MAX_VALUE);
                int maxHeight = Numbers.safeParseInteger(parameters.get("max-height"), Integer.MAX_VALUE);
diff --git a/src/main/java/net/pterodactylus/sone/template/ProfileAccessor.java b/src/main/java/net/pterodactylus/sone/template/ProfileAccessor.java
new file mode 100644 (file)
index 0000000..97be0cd
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * Sone - ProfileAccessor.java - Copyright © 2011 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.template;
+
+import net.pterodactylus.sone.core.Core;
+import net.pterodactylus.sone.data.Profile;
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.data.Sone.ShowCustomAvatars;
+import net.pterodactylus.sone.freenet.wot.Trust;
+import net.pterodactylus.util.template.Accessor;
+import net.pterodactylus.util.template.ReflectionAccessor;
+import net.pterodactylus.util.template.TemplateContext;
+
+/**
+ * {@link Accessor} for {@link Profile} objects that overwrites the original
+ * “avatar” member to include checks for whether the custom avatar should
+ * actually be shown.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class ProfileAccessor extends ReflectionAccessor {
+
+       /** The core. */
+       private final Core core;
+
+       /**
+        * Creates a new profile accessor.
+        *
+        * @param core
+        *            The Sone core
+        */
+       public ProfileAccessor(Core core) {
+               this.core = core;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Object get(TemplateContext templateContext, Object object, String member) {
+               Profile profile = (Profile) object;
+               if ("avatar".equals(member)) {
+                       Sone currentSone = (Sone) templateContext.get("currentSone");
+                       if (currentSone == null) {
+                               /* not logged in? don’t show custom avatars, then. */
+                               return null;
+                       }
+                       Sone remoteSone = profile.getSone();
+                       if (core.isLocalSone(remoteSone)) {
+                               /* always show your own avatars. */
+                               return profile.getAvatar();
+                       }
+                       ShowCustomAvatars showCustomAvatars = currentSone.getOptions().<ShowCustomAvatars> getEnumOption("ShowCustomAvatars").get();
+                       if (showCustomAvatars == ShowCustomAvatars.NEVER) {
+                               return null;
+                       }
+                       String avatarId = profile.getAvatar();
+                       if ((showCustomAvatars == ShowCustomAvatars.ALWAYS) || (avatarId == null)) {
+                               return avatarId;
+                       }
+                       if ((showCustomAvatars == ShowCustomAvatars.FOLLOWED) && currentSone.hasFriend(remoteSone.getId())) {
+                               return avatarId;
+                       }
+                       Trust trust = core.getTrust(currentSone, remoteSone);
+                       if (trust == null) {
+                               return null;
+                       }
+                       if ((showCustomAvatars == ShowCustomAvatars.MANUALLY_TRUSTED) && (trust.getExplicit() != null) && (trust.getExplicit() > 0)) {
+                               return avatarId;
+                       }
+                       if ((showCustomAvatars == ShowCustomAvatars.TRUSTED) && ((trust.getExplicit() != null) && (trust.getExplicit() > 0)) || ((trust.getImplicit() != null) && (trust.getImplicit() > 0))) {
+                               return avatarId;
+                       }
+                       return null;
+               }
+               return super.get(templateContext, object, member);
+       }
+
+}
index 59c7e3e..422004b 100644 (file)
@@ -67,6 +67,7 @@ public class EditProfilePage extends SoneTemplatePage {
                Integer birthDay = profile.getBirthDay();
                Integer birthMonth = profile.getBirthMonth();
                Integer birthYear = profile.getBirthYear();
+               String avatarId = profile.getAvatar();
                List<Field> fields = profile.getFields();
                if (request.getMethod() == Method.POST) {
                        if (request.getHttpRequest().getPartAsStringFailsafe("save-profile", 4).equals("true")) {
@@ -76,10 +77,12 @@ public class EditProfilePage extends SoneTemplatePage {
                                birthDay = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("birth-day", 256).trim());
                                birthMonth = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("birth-month", 256).trim());
                                birthYear = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("birth-year", 256).trim());
+                               avatarId = request.getHttpRequest().getPartAsStringFailsafe("avatar-id", 36);
                                profile.setFirstName(firstName.length() > 0 ? firstName : null);
                                profile.setMiddleName(middleName.length() > 0 ? middleName : null);
                                profile.setLastName(lastName.length() > 0 ? lastName : null);
                                profile.setBirthDay(birthDay).setBirthMonth(birthMonth).setBirthYear(birthYear);
+                               profile.setAvatar(webInterface.getCore().getImage(avatarId, false));
                                for (Field field : fields) {
                                        String value = request.getHttpRequest().getPartAsStringFailsafe("field-" + field.getId(), 400);
                                        field.setValue(value);
@@ -136,6 +139,7 @@ public class EditProfilePage extends SoneTemplatePage {
                templateContext.set("birthDay", birthDay);
                templateContext.set("birthMonth", birthMonth);
                templateContext.set("birthYear", birthYear);
+               templateContext.set("avatar-id", avatarId);
                templateContext.set("fields", fields);
        }
 
index 49b1275..1847b9c 100644 (file)
@@ -22,6 +22,7 @@ import java.util.List;
 
 import net.pterodactylus.sone.core.Core.Preferences;
 import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.data.Sone.ShowCustomAvatars;
 import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired;
 import net.pterodactylus.sone.web.page.FreenetRequest;
 import net.pterodactylus.util.number.Numbers;
@@ -73,6 +74,8 @@ public class OptionsPage extends SoneTemplatePage {
                                currentSone.getOptions().getBooleanOption("ShowNotification/NewPosts").set(showNotificationNewPosts);
                                boolean showNotificationNewReplies = request.getHttpRequest().isPartSet("show-notification-new-replies");
                                currentSone.getOptions().getBooleanOption("ShowNotification/NewReplies").set(showNotificationNewReplies);
+                               String showCustomAvatars = request.getHttpRequest().getPartAsStringFailsafe("show-custom-avatars", 32);
+                               currentSone.getOptions().<ShowCustomAvatars> getEnumOption("ShowCustomAvatars").set(ShowCustomAvatars.valueOf(showCustomAvatars));
                                webInterface.getCore().touchConfiguration();
                        }
                        Integer insertionDelay = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("insertion-delay", 16));
@@ -139,6 +142,7 @@ public class OptionsPage extends SoneTemplatePage {
                        templateContext.set("show-notification-new-sones", currentSone.getOptions().getBooleanOption("ShowNotification/NewSones").get());
                        templateContext.set("show-notification-new-posts", currentSone.getOptions().getBooleanOption("ShowNotification/NewPosts").get());
                        templateContext.set("show-notification-new-replies", currentSone.getOptions().getBooleanOption("ShowNotification/NewReplies").get());
+                       templateContext.set("show-custom-avatars", currentSone.getOptions().<ShowCustomAvatars> getEnumOption("ShowCustomAvatars").get().name());
                }
                templateContext.set("insertion-delay", preferences.getInsertionDelay());
                templateContext.set("posts-per-page", preferences.getPostsPerPage());
index 094d2a4..a007222 100644 (file)
@@ -41,6 +41,7 @@ import net.pterodactylus.sone.data.Album;
 import net.pterodactylus.sone.data.Image;
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.PostReply;
+import net.pterodactylus.sone.data.Profile;
 import net.pterodactylus.sone.data.Reply;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.freenet.L10nFilter;
@@ -58,6 +59,7 @@ import net.pterodactylus.sone.template.ImageLinkFilter;
 import net.pterodactylus.sone.template.JavascriptFilter;
 import net.pterodactylus.sone.template.ParserFilter;
 import net.pterodactylus.sone.template.PostAccessor;
+import net.pterodactylus.sone.template.ProfileAccessor;
 import net.pterodactylus.sone.template.ReplyAccessor;
 import net.pterodactylus.sone.template.ReplyGroupFilter;
 import net.pterodactylus.sone.template.RequestChangeFilter;
@@ -236,6 +238,7 @@ public class WebInterface implements CoreListener {
                templateContextFactory.addAccessor(Identity.class, new IdentityAccessor(getCore()));
                templateContextFactory.addAccessor(Trust.class, new TrustAccessor());
                templateContextFactory.addAccessor(HTTPRequest.class, new HttpRequestAccessor());
+               templateContextFactory.addAccessor(Profile.class, new ProfileAccessor(getCore()));
                templateContextFactory.addFilter("date", new DateFilter());
                templateContextFactory.addFilter("html", new HtmlFilter());
                templateContextFactory.addFilter("replace", new ReplaceFilter());
@@ -251,7 +254,7 @@ public class WebInterface implements CoreListener {
                templateContextFactory.addFilter("unknown", new UnknownDateFilter(getL10n(), "View.Sone.Text.UnknownDate"));
                templateContextFactory.addFilter("format", new FormatFilter());
                templateContextFactory.addFilter("sort", new CollectionSortFilter());
-               templateContextFactory.addFilter("image-link", new ImageLinkFilter(templateContextFactory));
+               templateContextFactory.addFilter("image-link", new ImageLinkFilter(getCore(), templateContextFactory));
                templateContextFactory.addFilter("replyGroup", new ReplyGroupFilter());
                templateContextFactory.addFilter("in", new ContainsFilter());
                templateContextFactory.addFilter("unique", new UniqueElementFilter());
index 3bb04b7..c3c1bc1 100644 (file)
@@ -43,6 +43,13 @@ Page.Options.Option.EnableSoneInsertNotifications.Description=If enabled, this w
 Page.Options.Option.ShowNotificationNewSones.Description=Show notifications for new Sones.
 Page.Options.Option.ShowNotificationNewPosts.Description=Show notifications for new posts.
 Page.Options.Option.ShowNotificationNewReplies.Description=Show notifications for new replies.
+Page.Options.Section.AvatarOptions.Title=Avatar Options
+Page.Options.Option.ShowAvatars.Description=You can disable custom avatars here, depending on the selected criteria. If an avatar is disabled for a Sone, the automatically generated avatar is shown instead.
+Page.Options.Option.ShowAvatars.Never.Description=Never show custom avatars.
+Page.Options.Option.ShowAvatars.Followed.Description=Only show avatars for Sones that you follow.
+Page.Options.Option.ShowAvatars.ManuallyTrusted.Description=Only show avatars for Sones that you have manually assigned a trust value larger than 0 to.
+Page.Options.Option.ShowAvatars.Trusted.Description=Only show avatars for Sones that have a trust value larger than 0.
+Page.Options.Option.ShowAvatars.Always.Description=Always show custom avatars. Be warned: some avatars might contain disturbing or offensive imagery.
 Page.Options.Section.RuntimeOptions.Title=Runtime Behaviour
 Page.Options.Option.InsertionDelay.Description=The number of seconds the Sone inserter waits after a modification of a Sone before it is being inserted.
 Page.Options.Option.PostsPerPage.Description=The number of posts to display on a page before pagination controls are being shown.
@@ -117,6 +124,9 @@ Page.EditProfile.Birthday.Title=Birthday
 Page.EditProfile.Birthday.Label.Day=Day:
 Page.EditProfile.Birthday.Label.Month=Month:
 Page.EditProfile.Birthday.Label.Year=Year:
+Page.EditProfile.Avatar.Title=Avatar
+Page.EditProfile.Avatar.Description=You can select one of your uploaded images to be shown as avatar. It should not be larger than 64×64 pixels because that is the largest size shown for other people (80×80 pixels is used for the page header).
+Page.EditProfile.Avatar.Delete=No avatar
 Page.EditProfile.Fields.Title=Custom Fields
 Page.EditProfile.Fields.Description=Here you can enter custom fields into your profile. These fields can contain anything you want and be as terse or as verbose as you wish. Just remember that when it comes to anonymity, sometimes less is more.
 Page.EditProfile.Fields.Button.Edit=edit
index e46540a..7754528 100644 (file)
@@ -171,6 +171,14 @@ textarea {
        margin-bottom: 1ex;
 }
 
+#sone .profile-avatar {
+       display: inline-block;
+       width: 80px;
+       height: 80px;
+       overflow: hidden;
+       position: absolute;
+}
+
 #sone .profile-link {
        font-weight: bold;
        color: rgb(28, 131, 191);
@@ -280,6 +288,13 @@ textarea {
        margin-right: 1ex;
 }
 
+#sone .menu-avatar {
+       display: inline-block;
+       width: 64px;
+       height: 64px;
+       overflow: hidden;
+}
+
 #sone .post .sone-menu .inner-menu {
        margin-left: 64px;
        padding-left: 1ex;
@@ -294,6 +309,13 @@ textarea {
        position: absolute;
 }
 
+#sone .post-avatar {
+       display: inline-block;
+       width: 48px;
+       height: 48px;
+       overflow: hidden;
+}
+
 #sone .post > .inner-part {
        margin-left: 48px;
        padding-left: 0.5em;
@@ -438,6 +460,13 @@ textarea {
        position: absolute;
 }
 
+#sone .reply-avatar {
+       display: inline-block;
+       width: 36px;
+       height: 36px;
+       overflow: hidden;
+}
+
 #sone .post .reply > .inner-part {
        margin-left: 36px;
        padding-left: 0.5em;
@@ -816,3 +845,28 @@ textarea {
 #sone #sort-options {
        margin-bottom: 1em;
 }
+
+#sone ul#avatar-selection {
+       padding: 0;
+}
+
+#sone #avatar-selection li {
+       display: inline-block;
+}
+
+#sone #avatar-selection li .post-avatar {
+       vertical-align: middle;
+       margin-top: 0.5em;
+}
+
+#sone #avatar-selection li#no-avatar {
+       display: block;
+}
+
+#sone form#options ul {
+       padding-left: 1em;
+}
+
+#sone form#options li {
+       list-style-type: none;
+}
index fea58a8..e140ac4 100644 (file)
                        <input type="text" name="birth-year" value="<% birthYear|html>" />
                </div>
 
+               <h1><%= Page.EditProfile.Avatar.Title|l10n|html></h1>
+
+               <p><%= Page.EditProfile.Avatar.Description|l10n|html></p>
+
+               <ul id="avatar-selection">
+                       <li id="no-avatar">
+                               <input type="radio" name="avatar-id" value="none"<%ifnull avatar-image> checked="checked"<%/if>/>
+                               <%= Page.EditProfile.Avatar.Delete|l10n|html>
+                       </li>
+                       <%foreach currentSone.allImages image>
+                               <li>
+                                       <input type="radio" name="avatar-id" value="<%image.id|html>"<%if avatar-id|match key=image.id> checked="checked"<%/if>/>
+                                       <div class="post-avatar"><% image|image-link max-width=48 max-height=48 mode=enlarge title==image.title></div>
+                               </li>
+                       <%/foreach>
+               </ul>
+
                <div>
                        <button type="submit" name="save-profile" value="true"><%= Page.EditProfile.Button.Save|l10n|html></button>
                </div>
index ae642dc..9988e68 100644 (file)
                </div>
 
                <div id="profile" class="<%ifnull currentSone>offline<%else>online<%/if>">
-                       <a class="picture" href="index.html">
-                               <%ifnull !currentSone>
-                                       <img src="/WebOfTrust/GetIdenticon?identity=<% currentSone.id|html>&amp;width=80&amp;height=80" width="80" height="80" alt="Profile Avatar" />
-                               <%else>
-                                       <img src="images/sone.png" width="80" height="80" alt="Sone is offline" />
-                               <%/if>
-                       </a>
+                       <div class="avatar profile-avatar">
+                               <a class="picture" href="index.html">
+                                       <%ifnull !currentSone>
+                                               <%ifnull !currentSone.profile.avatar>
+                                                       <%currentSone.profile.avatar|image-link max-width=80 max-height=80 mode=enlarge title="Profile Avatar">
+                                               <%else>
+                                                       <img src="/WebOfTrust/GetIdenticon?identity=<% currentSone.id|html>&amp;width=80&amp;height=80" width="80" height="80" alt="Profile Avatar" />
+                                               <%/if>
+                                       <%else>
+                                               <img src="images/sone.png" width="80" height="80" alt="Sone is offline" />
+                                       <%/if>
+                               </a>
+                       </div>
                        <%ifnull ! currentSone>
                                <div id="home-sone">
                                        <% currentSone|store key=sone>
index 128e389..9fa982f 100644 (file)
@@ -1,6 +1,12 @@
 <div class="sone-menu <% class|css|html>">
        <div class="sone-menu-id hidden"><%sone.id|html></div>
-       <img class="avatar" src="/WebOfTrust/GetIdenticon?identity=<%sone.id|html>&amp;width=64&amp;height=64" width="64" height="64" alt="Avatar Image" />
+       <div class="avatar menu-avatar">
+               <%ifnull !sone.profile.avatar>
+                       <%sone.profile.avatar|image-link max-width=64 max-height=64 mode=enlarge title==sone.niceName>
+               <%else>
+                       <img src="/WebOfTrust/GetIdenticon?identity=<%sone.id|html>&amp;width=64&amp;height=64" width="64" height="64" alt="Avatar Image" />
+               <%/if>
+       </div>
        <div class="inner-menu">
                <div>
                        <a class="author" href="viewSone.html?sone=<%sone.id|html>"><%sone.niceName|html></a>
index a478bba..396fa6e 100644 (file)
@@ -6,7 +6,11 @@
        <%include include/soneMenu.html class=="sone-post-menu" sone=post.sone>
        <div class="avatar post-avatar" >
                <%if post.loaded>
-                       <img src="/WebOfTrust/GetIdenticon?identity=<% post.sone.id|html>&amp;width=48&height=48" width="48" height="48" alt="Avatar Image" />
+                       <%ifnull !post.sone.profile.avatar>
+                               <%post.sone.profile.avatar|image-link max-width=48 max-height=48 mode=enlarge title="Avatar Image">
+                       <%else>
+                               <img src="/WebOfTrust/GetIdenticon?identity=<% post.sone.id|html>&amp;width=48&height=48" width="48" height="48" alt="Avatar Image" />
+                       <%/if>
                <%else>
                        <img src="images/sone-avatar.png" width="48" height="48" alt="Avatar Image" />
                <%/if>
index f8e3e64..ca07cf7 100644 (file)
@@ -5,7 +5,11 @@
        <div class="reply-author-local hidden"><% reply.sone.local></div>
        <%include include/soneMenu.html class=="sone-reply-menu" sone=reply.sone>
        <div class="avatar reply-avatar">
-               <img src="/WebOfTrust/GetIdenticon?identity=<% reply.sone.id|html>&amp;width=36&height=36" width="36" height="36" alt="Avatar Image" />
+               <%ifnull !reply.sone.profile.avatar>
+                       <% reply.sone.profile.avatar|image-link max-width=36 max-height=36 mode=enlarge title="Avatar Image">
+               <%else>
+                       <img src="/WebOfTrust/GetIdenticon?identity=<% reply.sone.id|html>&amp;width=36&height=36" width="36" height="36" alt="Avatar Image" />
+               <%/if>
        </div>
        <div class="inner-part">
                <div>
index 2e4f298..e615725 100644 (file)
@@ -16,6 +16,7 @@
                <birth-day><% currentSone.profile.birthDay|xml></birth-day>
                <birth-month><% currentSone.profile.birthMonth|xml></birth-month>
                <birth-year><% currentSone.profile.birthYear|xml></birth-year>
+               <avatar><%currentSone.profile.avatar|xml></avatar>
                <fields>
                        <%foreach currentSone.profile.fields field>
                        <field>
index 57f3c5f..94e01dc 100644 (file)
                        <%= Page.Options.Option.ShowNotificationNewReplies.Description|l10n|html>
                </p>
 
+               <h2><%= Page.Options.Section.AvatarOptions.Title|l10n|html></h2>
+
+               <p><%= Page.Options.Option.ShowAvatars.Description|l10n|html></p>
+               
+               <ul>
+                       <li>
+                               <input type="radio" name="show-custom-avatars" value="NEVER"<%if show-custom-avatars|match value=NEVER> checked="checked"<%/if>/>
+                               <%=Page.Options.Option.ShowAvatars.Never.Description|l10n|html>
+                       </li>
+                       <li>
+                               <input type="radio" name="show-custom-avatars" value="FOLLOWED"<%if show-custom-avatars|match value=FOLLOWED> checked="checked"<%/if>/>
+                               <%=Page.Options.Option.ShowAvatars.Followed.Description|l10n|html>
+                       </li>
+                       <li>
+                               <input type="radio" name="show-custom-avatars" value="MANUALLY_TRUSTED"<%if show-custom-avatars|match value=MANUALLY_TRUSTED> checked="checked"<%/if>/>
+                               <%=Page.Options.Option.ShowAvatars.ManuallyTrusted.Description|l10n|html>
+                       </li>
+                       <li>
+                               <input type="radio" name="show-custom-avatars" value="TRUSTED"<%if show-custom-avatars|match value=TRUSTED> checked="checked"<%/if>/>
+                               <%=Page.Options.Option.ShowAvatars.Trusted.Description|l10n|html>
+                       </li>
+                       <li>
+                               <input type="radio" name="show-custom-avatars" value="ALWAYS"<%if show-custom-avatars|match value=ALWAYS> checked="checked"<%/if>/>
+                               <%=Page.Options.Option.ShowAvatars.Always.Description|l10n|html>
+                       </li>
+               </ul>
+
                <h2><%= Page.Options.Section.RuntimeOptions.Title|l10n|html></h2>
 
                <p><%= Page.Options.Option.InsertionDelay.Description|l10n|html></p>