Make loading of linked images configurable
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Mon, 14 Nov 2016 06:00:35 +0000 (07:00 +0100)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Wed, 16 Nov 2016 19:24:50 +0000 (20:24 +0100)
21 files changed:
src/main/java/net/pterodactylus/sone/core/Core.java
src/main/java/net/pterodactylus/sone/data/SoneOptions.java
src/main/java/net/pterodactylus/sone/web/OptionsPage.java
src/main/kotlin/net/pterodactylus/sone/template/LinkedElementsFilter.kt
src/main/resources/i18n/sone.de.properties
src/main/resources/i18n/sone.en.properties
src/main/resources/i18n/sone.es.properties
src/main/resources/i18n/sone.fr.properties
src/main/resources/i18n/sone.ja.properties
src/main/resources/i18n/sone.no.properties
src/main/resources/i18n/sone.pl.properties
src/main/resources/i18n/sone.ru.properties
src/main/resources/templates/include/viewPost.html
src/main/resources/templates/include/viewReply.html
src/main/resources/templates/options.html
src/test/java/net/pterodactylus/sone/web/BookmarksPageTest.java
src/test/java/net/pterodactylus/sone/web/CreateSonePageTest.java
src/test/java/net/pterodactylus/sone/web/NewPageTest.java
src/test/java/net/pterodactylus/sone/web/WebPageTest.java
src/test/kotlin/net/pterodactylus/sone/template/LinkedElementsFilterTest.kt
src/test/kotlin/net/pterodactylus/sone/web/OptionsPageTest.kt [new file with mode: 0644]

index 8c4264a..a4fde13 100644 (file)
@@ -1071,6 +1071,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                sone.getOptions().setShowNewPostNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewPosts").getValue(true));
                sone.getOptions().setShowNewReplyNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewReplies").getValue(true));
                sone.getOptions().setShowCustomAvatars(LoadExternalContent.valueOf(configuration.getStringValue(sonePrefix + "/Options/ShowCustomAvatars").getValue(LoadExternalContent.NEVER.name())));
+               sone.getOptions().setLoadLinkedImages(LoadExternalContent.valueOf(configuration.getStringValue(sonePrefix + "/Options/LoadLinkedImages").getValue(LoadExternalContent.NEVER.name())));
 
                /* if we’re still here, Sone was loaded successfully. */
                synchronized (sone) {
@@ -1548,6 +1549,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewPosts").setValue(sone.getOptions().isShowNewPostNotifications());
                        configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewReplies").setValue(sone.getOptions().isShowNewReplyNotifications());
                        configuration.getStringValue(sonePrefix + "/Options/ShowCustomAvatars").setValue(sone.getOptions().getShowCustomAvatars().name());
+                       configuration.getStringValue(sonePrefix + "/Options/LoadLinkedImages").setValue(sone.getOptions().getLoadLinkedImages().name());
 
                        configuration.save();
 
index b5e42ee..5f05447 100644 (file)
@@ -2,6 +2,8 @@ package net.pterodactylus.sone.data;
 
 import static net.pterodactylus.sone.data.SoneOptions.LoadExternalContent.NEVER;
 
+import javax.annotation.Nonnull;
+
 /**
  * All Sone-specific options.
  *
@@ -27,6 +29,9 @@ public interface SoneOptions {
        LoadExternalContent getShowCustomAvatars();
        void setShowCustomAvatars(LoadExternalContent showCustomAvatars);
 
+       @Nonnull LoadExternalContent getLoadLinkedImages();
+       void setLoadLinkedImages(@Nonnull LoadExternalContent loadLinkedImages);
+
        /**
         * Possible values for all options that are related to loading external content.
         *
@@ -64,6 +69,7 @@ public interface SoneOptions {
                private boolean showNewPostNotifications = true;
                private boolean showNewReplyNotifications = true;
                private LoadExternalContent showCustomAvatars = NEVER;
+               private LoadExternalContent loadLinkedImages = NEVER;
 
                @Override
                public boolean isAutoFollow() {
@@ -125,6 +131,17 @@ public interface SoneOptions {
                        this.showCustomAvatars = showCustomAvatars;
                }
 
+               @Nonnull
+               @Override
+               public LoadExternalContent getLoadLinkedImages() {
+                       return loadLinkedImages;
+               }
+
+               @Override
+               public void setLoadLinkedImages(@Nonnull LoadExternalContent loadLinkedImages) {
+                       this.loadLinkedImages = loadLinkedImages;
+               }
+
        }
 
 }
index 4aaf232..341bc0c 100644 (file)
@@ -76,6 +76,8 @@ public class OptionsPage extends SoneTemplatePage {
                                currentSone.getOptions().setShowNewReplyNotifications(showNotificationNewReplies);
                                String showCustomAvatars = request.getHttpRequest().getPartAsStringFailsafe("show-custom-avatars", 32);
                                currentSone.getOptions().setShowCustomAvatars(LoadExternalContent.valueOf(showCustomAvatars));
+                               String loadLinkedImages = request.getHttpRequest().getPartAsStringFailsafe("load-linked-images", 32);
+                               currentSone.getOptions().setLoadLinkedImages(LoadExternalContent.valueOf(loadLinkedImages));
                                webInterface.getCore().touchConfiguration();
                        }
                        Integer insertionDelay = parseInt(request.getHttpRequest().getPartAsStringFailsafe("insertion-delay", 16), null);
@@ -145,6 +147,7 @@ public class OptionsPage extends SoneTemplatePage {
                        templateContext.set("show-notification-new-posts", currentSone.getOptions().isShowNewPostNotifications());
                        templateContext.set("show-notification-new-replies", currentSone.getOptions().isShowNewReplyNotifications());
                        templateContext.set("show-custom-avatars", currentSone.getOptions().getShowCustomAvatars().name());
+                       templateContext.set("load-linked-images", currentSone.getOptions().getLoadLinkedImages().name());
                }
                templateContext.set("insertion-delay", preferences.getInsertionDelay());
                templateContext.set("posts-per-page", preferences.getPostsPerPage());
index ca94423..c9414ea 100644 (file)
@@ -2,6 +2,13 @@ package net.pterodactylus.sone.template
 
 import net.pterodactylus.sone.core.ElementLoader
 import net.pterodactylus.sone.core.LinkedElement
+import net.pterodactylus.sone.data.Sone
+import net.pterodactylus.sone.data.SoneOptions.LoadExternalContent.ALWAYS
+import net.pterodactylus.sone.data.SoneOptions.LoadExternalContent.FOLLOWED
+import net.pterodactylus.sone.data.SoneOptions.LoadExternalContent.MANUALLY_TRUSTED
+import net.pterodactylus.sone.data.SoneOptions.LoadExternalContent.NEVER
+import net.pterodactylus.sone.data.SoneOptions.LoadExternalContent.TRUSTED
+import net.pterodactylus.sone.freenet.wot.OwnIdentity
 import net.pterodactylus.sone.text.FreenetLinkPart
 import net.pterodactylus.sone.text.Part
 import net.pterodactylus.util.template.Filter
@@ -15,10 +22,36 @@ class LinkedElementsFilter(private val elementLoader: ElementLoader) : Filter {
 
        @Suppress("UNCHECKED_CAST")
        override fun format(templateContext: TemplateContext?, data: Any?, parameters: MutableMap<String, Any?>?) =
-                       (data as? Iterable<Part>)
-                                       ?.filterIsInstance<FreenetLinkPart>()
-                                       ?.map { elementLoader.loadElement(it.link) }
-                                       ?.filter { !it.failed }
-                                       ?: listOf<LinkedElement>()
+                       if (showLinkedImages(templateContext?.get("currentSone") as Sone?, parameters?.get("sone") as Sone?)) {
+                               (data as? Iterable<Part>)
+                                               ?.filterIsInstance<FreenetLinkPart>()
+                                               ?.map { elementLoader.loadElement(it.link) }
+                                               ?.filter { !it.failed }
+                                               ?: listOf<LinkedElement>()
+                       } else {
+                               listOf<LinkedElement>()
+                       }
+
+       private fun showLinkedImages(currentSone: Sone?, sone: Sone?): Boolean {
+               return (currentSone != null) && (sone != null) && ((currentSone == sone) || currentSoneAllowsImagesFromSone(currentSone, sone))
+       }
+
+       private fun currentSoneAllowsImagesFromSone(currentSone: Sone, externalSone: Sone) =
+                       when (currentSone.options.loadLinkedImages) {
+                               NEVER -> false
+                               MANUALLY_TRUSTED -> externalSone.isLocal || currentSone.explicitelyTrusts(externalSone)
+                               FOLLOWED -> externalSone.isLocal || currentSone.hasFriend(externalSone.id)
+                               TRUSTED -> externalSone.isLocal || currentSone.implicitelyTrusts(externalSone)
+                               ALWAYS -> true
+                       }
+
+       private fun Sone.implicitelyTrusts(other: Sone): Boolean {
+               val explicitTrust = other.identity.getTrust(this.identity as OwnIdentity)?.explicit
+               val implicitTrust = other.identity.getTrust(this.identity as OwnIdentity)?.implicit
+               return ((explicitTrust != null) && (explicitTrust > 0)) || ((explicitTrust == null) && (implicitTrust != null) && (implicitTrust > 0))
+       }
+
+       private fun Sone.explicitelyTrusts(other: Sone) =
+                       other.identity.getTrust(this.identity as OwnIdentity)?.explicit ?: -1 > 0
 
 }
index 62e9807..aabcd67 100644 (file)
@@ -52,6 +52,13 @@ Page.Options.Option.ShowAvatars.Followed.Description=Nur benutzerdefinierte Avat
 Page.Options.Option.ShowAvatars.ManuallyTrusted.Description=Nur benutzerdefinierte Avatare von Sones, denen Sie manuell einen Vertrauenswert von mehr als 0 zugewiesen haben, anzeigen.
 Page.Options.Option.ShowAvatars.Trusted.Description=Nur benutzerdefinierte Avatare von Sones, die einen berechneten Vertrauenswert von mehr als 0 haben, anzeigen.
 Page.Options.Option.ShowAvatars.Always.Description=Immer benutzerdefinierte Avatare anzeigen. Warnung: Benutzerdefinierte Avatare können beliebiges Bildmaterial enthalten!
+Page.Options.Section.LoadLinkedImagesOptions.Title=Verlinkte Bilder laden
+Page.Options.Option.LoadLinkedImages.Description=Sone kann automatisch in Nachrichten und Antworten verlinkte Bilder laden. Diese Bilder werden nur aus Freenet geladen, niemals aus dem Internet!
+Page.Options.Option.LoadLinkedImages.Never.Description=Niemals verlinkte Bilder laden.
+Page.Options.Option.LoadLinkedImages.Followed.Description=Nur Bilder aus Nachrichten von Sones, denen Sie folgen, laden.
+Page.Options.Option.LoadLinkedImages.ManuallyTrusted.Description=Nur Bilder aus Nachrichten von Sones, denen Sie manuell einen Vertrauenswert von mehr als 0 zugewiesen haben, laden.
+Page.Options.Option.LoadLinkedImages.Trusted.Description=Nur Bilder aus Nachrichten von Sones, die einen berechneten Vertrauenswert von mehr als 0 haben, laden.
+Page.Options.Option.LoadLinkedImages.Always.Description=Immer verlinkte Bilder laden. Warnung: Verlinkte Bilder können beliebiges Bildmaterial enthalten!
 Page.Options.Section.RuntimeOptions.Title=Laufzeitverhalten
 Page.Options.Option.InsertionDelay.Description=Anzahl der Sekunden, die vor dem Hochladen einer Sone nach einer Änderung gewartet wird.
 Page.Options.Option.PostsPerPage.Description=Anzahl der Nachrichten pro Seite.
index ae57376..f7695a0 100644 (file)
@@ -52,6 +52,13 @@ Page.Options.Option.ShowAvatars.Followed.Description=Only show avatars for Sones
 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.LoadLinkedImagesOptions.Title=Load Linked Images
+Page.Options.Option.LoadLinkedImages.Description=Sone can automatically try to load images that are linked to in posts and replies. This will only ever load images from Freenet, never from the internet!
+Page.Options.Option.LoadLinkedImages.Never.Description=Never load linked images.
+Page.Options.Option.LoadLinkedImages.Followed.Description=Only load images linked from Sones that you follow.
+Page.Options.Option.LoadLinkedImages.ManuallyTrusted.Description=Only load images linked from Sones that you have manually assigned a trust value larger than 0 to.
+Page.Options.Option.LoadLinkedImages.Trusted.Description=Only load images linked from Sones that have a trust value larger than 0.
+Page.Options.Option.LoadLinkedImages.Always.Description=Always load linked images. Be warned: some images might be disturbing or considered offensive.
 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.
index ab992e4..7649bad 100644 (file)
@@ -52,6 +52,13 @@ Page.Options.Option.ShowAvatars.Followed.Description=Solo mostrar avatares de lo
 Page.Options.Option.ShowAvatars.ManuallyTrusted.Description=Solo mostrar avatares de Sones a los que has asignado manualmente un valor de veracidad mayor que 0.
 Page.Options.Option.ShowAvatars.Trusted.Description=Solo mostrar avatares para Sones que tienen un valor de veracidad mayor que 0.
 Page.Options.Option.ShowAvatars.Always.Description=Mostrar siempre avatares customizados. Advertencia, algunos avatares pueden contener imágenes perturbadoras o ofensivas.
+Page.Options.Section.LoadLinkedImagesOptions.Title=Load Linked Images
+Page.Options.Option.LoadLinkedImages.Description=Sone can automatically try to load images that are linked to in posts and replies. This will only ever load images from Freenet, never from the internet!
+Page.Options.Option.LoadLinkedImages.Never.Description=Never load linked images.
+Page.Options.Option.LoadLinkedImages.Followed.Description=Only load images linked from Sones that you follow.
+Page.Options.Option.LoadLinkedImages.ManuallyTrusted.Description=Only load images linked from Sones that you have manually assigned a trust value larger than 0 to.
+Page.Options.Option.LoadLinkedImages.Trusted.Description=Only load images linked from Sones that have a trust value larger than 0.
+Page.Options.Option.LoadLinkedImages.Always.Description=Always load linked images. Be warned: some images might be disturbing or considered offensive.
 Page.Options.Section.RuntimeOptions.Title=Comportamiento de la ejecución.
 Page.Options.Option.InsertionDelay.Description=Número de segundos que se esperarán para insertar el Sone tras una modificación.
 Page.Options.Option.PostsPerPage.Description=Número de publicaciones que se mostrarán en una página antes de que se muestren los controles de navegación.
@@ -464,3 +471,4 @@ Notification.Mention.Text=Has sido mencionado en las siguientes publicaciones:
 Notification.SoneIsInserting.Text=Tu Sone sone://{0} está siendo insertado.
 Notification.SoneIsInserted.Text=Tu Sone sone://{0} ha sido insertado en {1,number} {1,choice,0#segundos|1#segundo|1<segundos}.
 Notification.SoneInsertAborted.Text=Tu Sone sone://{0} no pudo ser insertado.
+# 55-61
index e068e49..8ec579d 100644 (file)
@@ -52,6 +52,13 @@ Page.Options.Option.ShowAvatars.Followed.Description=Ne montrer que les avatars
 Page.Options.Option.ShowAvatars.ManuallyTrusted.Description=Ne montrer que les avatars des Sones auxquelles vous êtes assignés manuellement une confiance superieure à 0.
 Page.Options.Option.ShowAvatars.Trusted.Description=Ne montrer que les avatars des Sones qui ont une confiance superieure à 0.
 Page.Options.Option.ShowAvatars.Always.Description=Montre tout le temps les avatars personnalisés. Attention: certains avatars peuvent être offensants !
+Page.Options.Section.LoadLinkedImagesOptions.Title=Load Linked Images
+Page.Options.Option.LoadLinkedImages.Description=Sone can automatically try to load images that are linked to in posts and replies. This will only ever load images from Freenet, never from the internet!
+Page.Options.Option.LoadLinkedImages.Never.Description=Never load linked images.
+Page.Options.Option.LoadLinkedImages.Followed.Description=Only load images linked from Sones that you follow.
+Page.Options.Option.LoadLinkedImages.ManuallyTrusted.Description=Only load images linked from Sones that you have manually assigned a trust value larger than 0 to.
+Page.Options.Option.LoadLinkedImages.Trusted.Description=Only load images linked from Sones that have a trust value larger than 0.
+Page.Options.Option.LoadLinkedImages.Always.Description=Always load linked images. Be warned: some images might be disturbing or considered offensive.
 Page.Options.Section.RuntimeOptions.Title=Comportement runtime
 Page.Options.Option.InsertionDelay.Description=Le délai d'insertion d'un Sone.
 Page.Options.Option.PostsPerPage.Description=Le nombre de message à afficher par page avant que les boutons de pagination soit affichés.
@@ -464,3 +471,4 @@ Notification.Mention.Text=Vous avez été mentionné dans les messages suivants:
 Notification.SoneIsInserting.Text=Votre Sone sone://{0} va maintenant être inséré.
 Notification.SoneIsInserted.Text=votre Sone sone://{0} a été inséré dans {1,number} {1,choice,0#seconds|1#second|1<seconds}.
 Notification.SoneInsertAborted.Text=Votre Sone sone://{0} ne peut pas être inséré.
+# 55-61
index d193e6f..c78575f 100644 (file)
@@ -52,6 +52,13 @@ Page.Options.Option.ShowAvatars.Followed.Description=フォローしているSon
 Page.Options.Option.ShowAvatars.ManuallyTrusted.Description=信用値を手動で0以上に設定しているSoneのみ、カスタムアイコンを表示する。
 Page.Options.Option.ShowAvatars.Trusted.Description=信用値が0以上のSoneのみ、カスタムアイコンを表示する。
 Page.Options.Option.ShowAvatars.Always.Description=常にカスタムアイコンを表示する。注意:不快な画像が表示される可能性があります。
+Page.Options.Section.LoadLinkedImagesOptions.Title=Load Linked Images
+Page.Options.Option.LoadLinkedImages.Description=Sone can automatically try to load images that are linked to in posts and replies. This will only ever load images from Freenet, never from the internet!
+Page.Options.Option.LoadLinkedImages.Never.Description=Never load linked images.
+Page.Options.Option.LoadLinkedImages.Followed.Description=Only load images linked from Sones that you follow.
+Page.Options.Option.LoadLinkedImages.ManuallyTrusted.Description=Only load images linked from Sones that you have manually assigned a trust value larger than 0 to.
+Page.Options.Option.LoadLinkedImages.Trusted.Description=Only load images linked from Sones that have a trust value larger than 0.
+Page.Options.Option.LoadLinkedImages.Always.Description=Always load linked images. Be warned: some images might be disturbing or considered offensive.
 Page.Options.Section.RuntimeOptions.Title=実行の挙動
 Page.Options.Option.InsertionDelay.Description=Soneを変更した後にインサートが開始されるまでの遅延時間(秒)。
 Page.Options.Option.PostsPerPage.Description=ページ送りのボタンが表示されるまでに表示する投稿の数。
@@ -464,4 +471,4 @@ Notification.Mention.Text=次の投稿でメンションされています:
 Notification.SoneIsInserting.Text=あなたのSone sone://{0}は現在インサート中です。
 Notification.SoneIsInserted.Text=あなたのSone sone://{0}は{1,number}{1,choice,0#秒|1#秒|1<秒}でインサートされました。
 Notification.SoneInsertAborted.Text=あなたのSone sone://{0}のインサートに失敗しました。
-# 60, 100, 458
+# 55-51, 67, 107, 465
index 99a950f..59ddcda 100644 (file)
@@ -52,6 +52,13 @@ Page.Options.Option.ShowAvatars.Followed.Description=Bare vis avatar for Soner d
 Page.Options.Option.ShowAvatars.ManuallyTrusted.Description=Bare vis avatar for Soner du manuelt har gitt en tillit større en 0.
 Page.Options.Option.ShowAvatars.Trusted.Description=Bare vis avatar for Soner som har en tillit større enn 0.
 Page.Options.Option.ShowAvatars.Always.Description=Alltid vis egendefinerte avatarer. Advarsel: Noen avatarer kan inneholde forstyrrende eller provoserende bilder.
+Page.Options.Section.LoadLinkedImagesOptions.Title=Load Linked Images
+Page.Options.Option.LoadLinkedImages.Description=Sone can automatically try to load images that are linked to in posts and replies. This will only ever load images from Freenet, never from the internet!
+Page.Options.Option.LoadLinkedImages.Never.Description=Never load linked images.
+Page.Options.Option.LoadLinkedImages.Followed.Description=Only load images linked from Sones that you follow.
+Page.Options.Option.LoadLinkedImages.ManuallyTrusted.Description=Only load images linked from Sones that you have manually assigned a trust value larger than 0 to.
+Page.Options.Option.LoadLinkedImages.Trusted.Description=Only load images linked from Sones that have a trust value larger than 0.
+Page.Options.Option.LoadLinkedImages.Always.Description=Always load linked images. Be warned: some images might be disturbing or considered offensive.
 Page.Options.Section.RuntimeOptions.Title=Oppførsel ved kjøretid
 Page.Options.Option.InsertionDelay.Description=Antall sekunder Sone-innsetteren skal vente etter en endring av en Sone før den blir innsatt.
 Page.Options.Option.PostsPerPage.Description=Antallet innlegg å vise pr side før side-kontroller blir vist.
@@ -464,4 +471,4 @@ Notification.Mention.Text=Du har blitt nevnt i følgende innlegg:
 Notification.SoneIsInserting.Text=Your Sone sone://{0} is now being inserted.
 Notification.SoneIsInserted.Text=Your Sone sone://{0} has been inserted in {1,number} {1,choice,0#seconds|1#second|1<seconds}.
 Notification.SoneInsertAborted.Text=Your Sone sone://{0} could not be inserted.
-# 60, 100, 120-121, 308-310, 312-314, 458, 463-465
+# 55-61, 67, 107, 127-128, 315-317, 319-321, 465, 471-473
index 82db1b5..1e78585 100644 (file)
@@ -52,6 +52,13 @@ Page.Options.Option.ShowAvatars.Followed.Description=Pokazuj avatary tylko śled
 Page.Options.Option.ShowAvatars.ManuallyTrusted.Description=Pokazuj avatary tylko tych użytkowników Sone, którym ręcznie przyznałeś ilość punktów zaufania przekraczającą 0.
 Page.Options.Option.ShowAvatars.Trusted.Description=Pokazuj avatary tylko tych użytkowników Sone, którzy mają ilość punktów zaufania większą niż 0.
 Page.Options.Option.ShowAvatars.Always.Description=Zawsze pokazuj niestandardowe avatary. Uwaga: niektóre avatary mogą zawierać obraźliwe treści.
+Page.Options.Section.LoadLinkedImagesOptions.Title=Load Linked Images
+Page.Options.Option.LoadLinkedImages.Description=Sone can automatically try to load images that are linked to in posts and replies. This will only ever load images from Freenet, never from the internet!
+Page.Options.Option.LoadLinkedImages.Never.Description=Never load linked images.
+Page.Options.Option.LoadLinkedImages.Followed.Description=Only load images linked from Sones that you follow.
+Page.Options.Option.LoadLinkedImages.ManuallyTrusted.Description=Only load images linked from Sones that you have manually assigned a trust value larger than 0 to.
+Page.Options.Option.LoadLinkedImages.Trusted.Description=Only load images linked from Sones that have a trust value larger than 0.
+Page.Options.Option.LoadLinkedImages.Always.Description=Always load linked images. Be warned: some images might be disturbing or considered offensive.
 Page.Options.Section.RuntimeOptions.Title=Tryb pracy
 Page.Options.Option.InsertionDelay.Description=Czas oczekiwania użytkownika Sone na modifikację profilu Sone przed jego załadowaniem.
 Page.Options.Option.PostsPerPage.Description=Ilość postów wyświetlanych na stronie przed pojawieniem się znaków paginacji.
@@ -464,4 +471,4 @@ Notification.Mention.Text=Zostałeś oznaczony w następujących postach:
 Notification.SoneIsInserting.Text=Twoje Sone sone://{0} jest w tej chili wysyłane.
 Notification.SoneIsInserted.Text=Twoje sone://{0} zostało wysłane w {1,number} {1,choice,0#seconds|1#second|1<seconds}.
 Notification.SoneInsertAborted.Text=Twoje Sone sone://{0} nie mogło zostać wysłane.
-# 458
+# 55-61, 465
index 85d82f1..8fe8f31 100644 (file)
@@ -52,6 +52,13 @@ Page.Options.Option.ShowAvatars.Followed.Description=Показывать ава
 Page.Options.Option.ShowAvatars.ManuallyTrusted.Description=Показывать аватары только для Sone, которым вы вручную задали значение доверия выше 0.
 Page.Options.Option.ShowAvatars.Trusted.Description=Показывать аватары только для Sone, значение доверия которых выше 0.
 Page.Options.Option.ShowAvatars.Always.Description=Всегда показывать пользовательские аватары. Предупреждение: некоторые аватары могут содержать раздражающие или оскорбительные изображения.
+Page.Options.Section.LoadLinkedImagesOptions.Title=Load Linked Images
+Page.Options.Option.LoadLinkedImages.Description=Sone can automatically try to load images that are linked to in posts and replies. This will only ever load images from Freenet, never from the internet!
+Page.Options.Option.LoadLinkedImages.Never.Description=Never load linked images.
+Page.Options.Option.LoadLinkedImages.Followed.Description=Only load images linked from Sones that you follow.
+Page.Options.Option.LoadLinkedImages.ManuallyTrusted.Description=Only load images linked from Sones that you have manually assigned a trust value larger than 0 to.
+Page.Options.Option.LoadLinkedImages.Trusted.Description=Only load images linked from Sones that have a trust value larger than 0.
+Page.Options.Option.LoadLinkedImages.Always.Description=Always load linked images. Be warned: some images might be disturbing or considered offensive.
 Page.Options.Section.RuntimeOptions.Title=Поведение во время работы.
 Page.Options.Option.InsertionDelay.Description=Количество секунд, в течение которых выгрузчик Sone ожидает после изменения Sone до того, как он будет выгружен.
 Page.Options.Option.PostsPerPage.Description=Количество сообщений, которое должно быть показно на странице до того, как будут показаны кнопки переключения страниц.
@@ -464,4 +471,4 @@ Notification.Mention.Text=Вас упомянули в следующих соо
 Notification.SoneIsInserting.Text=Your Sone sone://{0} is now being inserted.
 Notification.SoneIsInserted.Text=Your Sone sone://{0} has been inserted in {1,number} {1,choice,0#seconds|1#second|1<seconds}.
 Notification.SoneInsertAborted.Text=Your Sone sone://{0} could not be inserted.
-# 60, 100, 120-121, 308-310, 312-314, 458, 463-465
+# 55-61, 67, 107, 127-128, 315-317, 319-321, 465, 471-473
index 734a8b4..12d37db 100644 (file)
@@ -35,7 +35,7 @@
                        <div class="post-text short-text<%if raw> hidden<%/if><%if shortText|match key=renderedText> hidden<%/if>"><% shortText></div>
                        <%if !shortText|match value=renderedText><%if !raw><a class="expand-post-text" href="viewPost.html?post=<% post.id|html>&amp;raw=true"><%= View.Post.ShowMore|l10n|html></a><%/if><%/if>
                        <%if !shortText|match value=renderedText><%if !raw><a class="shrink-post-text hidden"><%= View.Post.ShowLess|l10n|html></a><%/if><%/if>
-                       <% parsedText|linked-elements|store key==linkedElements>
+                       <% parsedText|linked-elements sone=post.sone|store key==linkedElements>
                        <% foreach linkedElements linkedElement>
                                <% first>
                                        <div class="linked-elements">
index 6091d40..211c1d3 100644 (file)
@@ -23,7 +23,7 @@
                        <div class="reply-text short-text<%if raw> hidden<%/if><%if shortText|match key=renderedText> hidden<%/if>"><% shortText></div>
                        <%if !shortText|match value=renderedText><%if !raw><a class="expand-reply-text" href="viewPost.html?post=<% reply.postId|html>&amp;raw=true"><%= View.Post.ShowMore|l10n|html></a><%/if><%/if>
                        <%if !shortText|match value=renderedText><%if !raw><a class="shrink-reply-text hidden"><%= View.Post.ShowLess|l10n|html></a><%/if><%/if>
-                       <% parsedText|linked-elements|store key==linkedElements>
+                       <% parsedText|linked-elements sone=reply.sone|store key==linkedElements>
                        <% foreach linkedElements linkedElement>
                                <% first>
                                        <div class="linked-elements">
index 4ec88cb..34a1196 100644 (file)
                        </li>
                </ul>
 
+               <h2><%= Page.Options.Section.LoadLinkedImagesOptions.Title|l10n|html></h2>
+
+               <p><%= Page.Options.Option.LoadLinkedImages.Description|l10n|html></p>
+
+               <ul>
+                       <li>
+                               <input type="radio" name="load-linked-images" value="NEVER"<%if load-linked-images|match value==NEVER> checked="checked"<%/if>/>
+                               <%=Page.Options.Option.LoadLinkedImages.Never.Description|l10n|html>
+                       </li>
+                       <li>
+                               <input type="radio" name="load-linked-images" value="FOLLOWED"<%if load-linked-images|match value==FOLLOWED> checked="checked"<%/if>/>
+                               <%=Page.Options.Option.LoadLinkedImages.Followed.Description|l10n|html>
+                       </li>
+                       <li>
+                               <input type="radio" name="load-linked-images" value="MANUALLY_TRUSTED"<%if load-linked-images|match value==MANUALLY_TRUSTED> checked="checked"<%/if>/>
+                               <%=Page.Options.Option.LoadLinkedImages.ManuallyTrusted.Description|l10n|html>
+                       </li>
+                       <li>
+                               <input type="radio" name="load-linked-images" value="TRUSTED"<%if load-linked-images|match value==TRUSTED> checked="checked"<%/if>/>
+                               <%=Page.Options.Option.LoadLinkedImages.Trusted.Description|l10n|html>
+                       </li>
+                       <li>
+                               <input type="radio" name="load-linked-images" value="ALWAYS"<%if load-linked-images|match value==ALWAYS> checked="checked"<%/if>/>
+                               <%=Page.Options.Option.LoadLinkedImages.Always.Description|l10n|html>
+                       </li>
+               </ul>
+
                <h2><%= Page.Options.Section.RuntimeOptions.Title|l10n|html></h2>
 
                <p><%= Page.Options.Option.InsertionDelay.Description|l10n|html></p>
index 46b0a18..0c85a88 100644 (file)
@@ -38,7 +38,7 @@ public class BookmarksPageTest extends WebPageTest {
                Post post3 = createPost(true, 2000L);
                Set<Post> bookmarkedPosts = createBookmarkedPosts(post1, post2, post3);
                when(core.getBookmarkedPosts()).thenReturn(bookmarkedPosts);
-               when(core.getPreferences().getPostsPerPage()).thenReturn(5);
+               core.getPreferences().setPostsPerPage(5);
                page.processTemplate(freenetRequest, templateContext);
                assertThat((Collection<Post>) templateContext.get("posts"), contains(post1, post3, post2));
                assertThat(((Pagination<Post>) templateContext.get("pagination")).getItems(), contains(post1, post3, post2));
@@ -61,7 +61,7 @@ public class BookmarksPageTest extends WebPageTest {
                Post post3 = createPost(false, 2000L);
                Set<Post> bookmarkedPosts = createBookmarkedPosts(post1, post2, post3);
                when(core.getBookmarkedPosts()).thenReturn(bookmarkedPosts);
-               when(core.getPreferences().getPostsPerPage()).thenReturn(5);
+               core.getPreferences().setPostsPerPage(5);
                page.processTemplate(freenetRequest, templateContext);
                assertThat((Collection<Post>) templateContext.get("posts"), contains(post2, post1));
                assertThat(((Pagination<Post>) templateContext.get("pagination")).getItems(), contains(post2, post1));
index aab40c9..c7bb2f2 100644 (file)
@@ -132,14 +132,14 @@ public class CreateSonePageTest extends WebPageTest {
 
        @Test
        public void doNotShowCreateSoneInMenuIfFullAccessRequiredButClientHasNoFullAccess() {
-               when(core.getPreferences().isRequireFullAccess()).thenReturn(true);
+               core.getPreferences().setRequireFullAccess(true);
                when(toadletContext.isAllowedFullAccess()).thenReturn(false);
                assertThat(page.isEnabled(toadletContext), is(false));
        }
 
        @Test
        public void showCreateSoneInMenuIfNotLoggedInAndClientHasFullAccess() {
-               when(core.getPreferences().isRequireFullAccess()).thenReturn(true);
+               core.getPreferences().setRequireFullAccess(true);
                when(toadletContext.isAllowedFullAccess()).thenReturn(true);
                unsetCurrentSone();
                assertThat(page.isEnabled(toadletContext), is(true));
index e110969..223fcb2 100644 (file)
@@ -26,7 +26,7 @@ public class NewPageTest extends WebPageTest {
 
        @Before
        public void setupNumberOfPostsPerPage() {
-               when(webInterface.getCore().getPreferences().getPostsPerPage()).thenReturn(5);
+               webInterface.getCore().getPreferences().setPostsPerPage(5);
        }
 
        @Test
index 3e2df88..9dff571 100644 (file)
@@ -16,9 +16,11 @@ import java.util.List;
 import java.util.Set;
 
 import net.pterodactylus.sone.core.Core;
+import net.pterodactylus.sone.core.Preferences;
 import net.pterodactylus.sone.core.UpdateChecker;
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.data.SoneOptions.DefaultSoneOptions;
 import net.pterodactylus.sone.freenet.wot.OwnIdentity;
 import net.pterodactylus.sone.web.page.FreenetRequest;
 import net.pterodactylus.util.notify.Notification;
@@ -30,6 +32,7 @@ import freenet.clients.http.ToadletContext;
 import freenet.support.api.HTTPRequest;
 
 import com.google.common.base.Optional;
+import com.google.common.eventbus.EventBus;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.rules.ExpectedException;
@@ -48,6 +51,7 @@ public abstract class WebPageTest {
 
        protected final Template template = new Template();
        protected final WebInterface webInterface = mock(WebInterface.class, RETURNS_DEEP_STUBS);
+       protected final EventBus eventBus = mock(EventBus.class);
        protected final Core core = webInterface.getCore();
 
        protected final Sone currentSone = mock(Sone.class);
@@ -76,6 +80,7 @@ public abstract class WebPageTest {
        public final void setupCore() {
                UpdateChecker updateChecker = mock(UpdateChecker.class);
                when(core.getUpdateChecker()).thenReturn(updateChecker);
+               when(core.getPreferences()).thenReturn(new Preferences(eventBus));
                when(core.getLocalSone(anyString())).thenReturn(null);
                when(core.getLocalSones()).thenReturn(localSones);
                when(core.getSone(anyString())).thenReturn(Optional.<Sone>absent());
@@ -94,6 +99,11 @@ public abstract class WebPageTest {
                when(webInterface.getNotifications(currentSone)).thenReturn(new ArrayList<Notification>());
        }
 
+       @Before
+       public void setupSone() {
+               when(currentSone.getOptions()).thenReturn(new DefaultSoneOptions());
+       }
+
        protected void unsetCurrentSone() {
                when(webInterface.getCurrentSone(toadletContext)).thenReturn(null);
                when(webInterface.getCurrentSone(eq(toadletContext), anyBoolean())).thenReturn(null);
index 1f6f681..1e83b85 100644 (file)
@@ -2,12 +2,24 @@ package net.pterodactylus.sone.template
 
 import net.pterodactylus.sone.core.ElementLoader
 import net.pterodactylus.sone.core.LinkedElement
+import net.pterodactylus.sone.data.Sone
+import net.pterodactylus.sone.data.SoneOptions.DefaultSoneOptions
+import net.pterodactylus.sone.data.SoneOptions.LoadExternalContent.ALWAYS
+import net.pterodactylus.sone.data.SoneOptions.LoadExternalContent.FOLLOWED
+import net.pterodactylus.sone.data.SoneOptions.LoadExternalContent.MANUALLY_TRUSTED
+import net.pterodactylus.sone.data.SoneOptions.LoadExternalContent.TRUSTED
+import net.pterodactylus.sone.freenet.wot.OwnIdentity
+import net.pterodactylus.sone.freenet.wot.Trust
 import net.pterodactylus.sone.test.mock
 import net.pterodactylus.sone.text.FreenetLinkPart
 import net.pterodactylus.sone.text.LinkPart
+import net.pterodactylus.sone.text.Part
 import net.pterodactylus.sone.text.PlainTextPart
+import net.pterodactylus.util.template.TemplateContext
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.contains
+import org.hamcrest.Matchers.emptyIterable
+import org.junit.Before
 import org.junit.Test
 import org.mockito.Mockito.`when`
 
@@ -18,24 +30,218 @@ class LinkedElementsFilterTest {
 
        private val imageLoader = mock<ElementLoader>()
        private val filter = LinkedElementsFilter(imageLoader)
+       private val templateContext = TemplateContext()
+       private val parameters = mutableMapOf<String, Any?>()
+       private val sone = createSone()
+       private val remoteSone = createSone("remote-id")
+       private val parts: List<Part> = listOf(
+                       PlainTextPart("text"),
+                       LinkPart("http://link", "link"),
+                       FreenetLinkPart("KSK@link", "link", false),
+                       FreenetLinkPart("KSK@loading.png", "link", false),
+                       FreenetLinkPart("KSK@link.png", "link", false)
+       )
 
-       @Test
-       fun `filter finds all loaded freenet images`() {
-               val parts = listOf(
-                               PlainTextPart("text"),
-                               LinkPart("http://link", "link"),
-                               FreenetLinkPart("KSK@link", "link", false),
-                               FreenetLinkPart("KSK@loading.png", "link", false),
-                               FreenetLinkPart("KSK@link.png", "link", false)
-               )
+       @Before
+       fun setupSone() {
+               `when`(sone.options).thenReturn(DefaultSoneOptions())
+       }
+
+       @Before
+       fun setupImageLoader() {
                `when`(imageLoader.loadElement("KSK@link")).thenReturn(LinkedElement("KSK@link", failed = true))
                `when`(imageLoader.loadElement("KSK@loading.png")).thenReturn(LinkedElement("KSK@loading.png", loading = true))
                `when`(imageLoader.loadElement("KSK@link.png")).thenReturn(LinkedElement("KSK@link.png"))
-               val loadedImages = filter.format(null, parts, null)
+       }
+
+       @Test
+       fun `filter does not find any image if there is no template context`() {
+               assertThat(filter.format(null, parts, parameters), emptyIterable())
+       }
+
+       @Test
+       fun `filter does not find any image if there is no current sone`() {
+               verifyThatImagesAreNotPresent()
+       }
+
+       @Test
+       fun `filter does not find any images if there is no remote sone`() {
+               sone.options.loadLinkedImages = ALWAYS
+               templateContext.set("currentSone", sone)
+               verifyThatImagesAreNotPresent()
+       }
+
+       @Test
+       fun `filter does not find any images if sone does not allow to load images`() {
+               templateContext.set("currentSone", sone)
+               parameters["sone"] = remoteSone
+               verifyThatImagesAreNotPresent()
+       }
+
+       @Test
+       fun `filter finds all loaded freenet images from the sone itself`() {
+               templateContext.set("currentSone", sone)
+               parameters["sone"] = sone
+               verifyThatImagesArePresent()
+       }
+
+       @Test
+       fun `filter finds images if the remote sone is local`() {
+               sone.options.loadLinkedImages = MANUALLY_TRUSTED
+               templateContext.set("currentSone", sone)
+               `when`(remoteSone.isLocal).thenReturn(true)
+               parameters["sone"] = remoteSone
+               verifyThatImagesArePresent()
+       }
+
+       @Test
+       fun `filter does not find images if local sone requires manual trust and remote sone has not trust`() {
+               sone.options.loadLinkedImages = MANUALLY_TRUSTED
+               templateContext.set("currentSone", sone)
+               parameters["sone"] = remoteSone
+               verifyThatImagesAreNotPresent()
+       }
+
+       @Test
+       fun `filter does not find images if local sone requires manual trust and remote sone has only implicit trust`() {
+               sone.options.loadLinkedImages = MANUALLY_TRUSTED
+               templateContext.set("currentSone", sone)
+               `when`(remoteSone.identity.getTrust(this.sone.identity as OwnIdentity)).thenReturn(Trust(null, 100, null))
+               parameters["sone"] = remoteSone
+               verifyThatImagesAreNotPresent()
+       }
+
+       @Test
+       fun `filter does not find images if local sone requires manual trust and remote sone has explicit trust of zero`() {
+               sone.options.loadLinkedImages = MANUALLY_TRUSTED
+               templateContext.set("currentSone", sone)
+               `when`(remoteSone.identity.getTrust(this.sone.identity as OwnIdentity)).thenReturn(Trust(0, null, null))
+               parameters["sone"] = remoteSone
+               verifyThatImagesAreNotPresent()
+       }
+
+       @Test
+       fun `filter finds images if local sone requires manual trust and remote sone has explicit trust of one`() {
+               sone.options.loadLinkedImages = MANUALLY_TRUSTED
+               templateContext.set("currentSone", sone)
+               `when`(remoteSone.identity.getTrust(this.sone.identity as OwnIdentity)).thenReturn(Trust(1, null, null))
+               parameters["sone"] = remoteSone
+               verifyThatImagesArePresent()
+       }
+
+       @Test
+       fun `filter does not find images if local sone requires following and remote sone is not followed`() {
+           sone.options.loadLinkedImages = FOLLOWED
+               templateContext["currentSone"] = sone
+               parameters["sone"] = remoteSone
+               verifyThatImagesAreNotPresent()
+       }
+
+       @Test
+       fun `filter finds images if local sone requires following and remote sone is followed`() {
+           sone.options.loadLinkedImages = FOLLOWED
+               `when`(sone.hasFriend("remote-id")).thenReturn(true)
+               templateContext["currentSone"] = sone
+               parameters["sone"] = remoteSone
+               verifyThatImagesArePresent()
+       }
+
+       @Test
+       fun `filter finds images if local sone requires following and remote sone is the same as the local sone`() {
+           sone.options.loadLinkedImages = FOLLOWED
+               templateContext["currentSone"] = sone
+               parameters["sone"] = sone
+               verifyThatImagesArePresent()
+       }
+
+       @Test
+       fun `filter finds images if following is required and remote sone is a local sone`() {
+               sone.options.loadLinkedImages = FOLLOWED
+               templateContext["currentSone"] = sone
+               `when`(remoteSone.isLocal).thenReturn(true)
+               parameters["sone"] = remoteSone
+               verifyThatImagesArePresent()
+       }
+
+       @Test
+       fun `filter does not find images if any trust is required and remote sone does not have any trust`() {
+           sone.options.loadLinkedImages = TRUSTED
+               templateContext["currentSone"] = sone
+               parameters["sone"] = remoteSone
+               verifyThatImagesAreNotPresent()
+       }
+
+       @Test
+       fun `filter does not find images if any trust is required and remote sone has implicit trust of zero`() {
+           sone.options.loadLinkedImages = TRUSTED
+               templateContext["currentSone"] = sone
+               `when`(remoteSone.identity.getTrust(sone.identity as OwnIdentity)).thenReturn(Trust(null, 0, null))
+               parameters["sone"] = remoteSone
+               verifyThatImagesAreNotPresent()
+       }
+
+       @Test
+       fun `filter finds images if any trust is required and remote sone has implicit trust of one`() {
+           sone.options.loadLinkedImages = TRUSTED
+               templateContext["currentSone"] = sone
+               `when`(remoteSone.identity.getTrust(sone.identity as OwnIdentity)).thenReturn(Trust(null, 1, null))
+               parameters["sone"] = remoteSone
+               verifyThatImagesArePresent()
+       }
+
+       @Test
+       fun `filter does not find images if any trust is required and remote sone has explicit trust of zero but implicit trust of one`() {
+               sone.options.loadLinkedImages = TRUSTED
+               templateContext["currentSone"] = sone
+               `when`(remoteSone.identity.getTrust(sone.identity as OwnIdentity)).thenReturn(Trust(0, 1, null))
+               parameters["sone"] = remoteSone
+               verifyThatImagesAreNotPresent()
+       }
+
+       @Test
+       fun `filter finds images if any trust is required and remote sone has explicit trust of one but no implicit trust`() {
+               sone.options.loadLinkedImages = TRUSTED
+               templateContext["currentSone"] = sone
+               `when`(remoteSone.identity.getTrust(sone.identity as OwnIdentity)).thenReturn(Trust(1, null, null))
+               parameters["sone"] = remoteSone
+               verifyThatImagesArePresent()
+       }
+
+       @Test
+       fun `filter finds images if any trust is required and remote sone is a local sone`() {
+               sone.options.loadLinkedImages = TRUSTED
+               templateContext["currentSone"] = sone
+               `when`(remoteSone.isLocal).thenReturn(true)
+               parameters["sone"] = remoteSone
+               verifyThatImagesArePresent()
+       }
+
+       @Test
+       fun `filter finds images if no trust is required`() {
+           sone.options.loadLinkedImages = ALWAYS
+               templateContext["currentSone"] = sone
+               parameters["sone"] = remoteSone
+               verifyThatImagesArePresent()
+       }
+
+       private fun verifyThatImagesArePresent() {
+               val loadedImages = filter.format(templateContext, parts, parameters)
                assertThat(loadedImages, contains<LinkedElement>(
                                LinkedElement("KSK@loading.png", failed = false, loading = true),
                                LinkedElement("KSK@link.png", failed = false, loading = false)
                ))
        }
 
+       private fun verifyThatImagesAreNotPresent() {
+               assertThat(filter.format(templateContext, parts, parameters), emptyIterable())
+       }
+
+       private fun createSone(id: String = "sone-id"): Sone {
+               val sone = mock<Sone>()
+               `when`(sone.id).thenReturn(id)
+               `when`(sone.options).thenReturn(DefaultSoneOptions())
+               `when`(sone.identity).thenReturn(mock<OwnIdentity>())
+               return sone
+       }
+
 }
diff --git a/src/test/kotlin/net/pterodactylus/sone/web/OptionsPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/OptionsPageTest.kt
new file mode 100644 (file)
index 0000000..af57a3a
--- /dev/null
@@ -0,0 +1,33 @@
+package net.pterodactylus.sone.web
+
+import net.pterodactylus.sone.data.SoneOptions.LoadExternalContent.ALWAYS
+import net.pterodactylus.sone.web.WebTestUtils.redirectsTo
+import net.pterodactylus.util.web.Method.POST
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.`is`
+import org.junit.Test
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+/**
+ * Unit test for [OptionsPage].
+ */
+class OptionsPageTest : WebPageTest() {
+
+       private val page = OptionsPage(template, webInterface)
+
+       @Test
+       fun `options page sets correct value for load-linked-images`() {
+               request("", POST)
+               addHttpRequestParameter("show-custom-avatars", "ALWAYS")
+               addHttpRequestParameter("load-linked-images", "ALWAYS")
+               expectedException.expect(redirectsTo("options.html"))
+               try {
+                       page.handleRequest(freenetRequest, templateContext)
+               } finally {
+                       assertThat(currentSone.options.loadLinkedImages, `is`(ALWAYS))
+                       verify(core, times(2)).touchConfiguration()
+               }
+       }
+
+}