diff options
Diffstat (limited to 'test')
86 files changed, 13110 insertions, 988 deletions
diff --git a/test/emoji_test.exs b/test/emoji_test.exs new file mode 100644 index 000000000..cb1d62d00 --- /dev/null +++ b/test/emoji_test.exs @@ -0,0 +1,106 @@ +defmodule Pleroma.EmojiTest do +  use ExUnit.Case, async: true +  alias Pleroma.Emoji + +  describe "get_all/0" do +    setup do +      emoji_list = Emoji.get_all() +      {:ok, emoji_list: emoji_list} +    end + +    test "first emoji", %{emoji_list: emoji_list} do +      [emoji | _others] = emoji_list +      {code, path, tags} = emoji + +      assert tuple_size(emoji) == 3 +      assert is_binary(code) +      assert is_binary(path) +      assert is_binary(tags) +    end + +    test "random emoji", %{emoji_list: emoji_list} do +      emoji = Enum.random(emoji_list) +      {code, path, tags} = emoji + +      assert tuple_size(emoji) == 3 +      assert is_binary(code) +      assert is_binary(path) +      assert is_binary(tags) +    end +  end + +  describe "match_extra/2" do +    setup do +      groups = [ +        "list of files": ["/emoji/custom/first_file.png", "/emoji/custom/second_file.png"], +        "wildcard folder": "/emoji/custom/*/file.png", +        "wildcard files": "/emoji/custom/folder/*.png", +        "special file": "/emoji/custom/special.png" +      ] + +      {:ok, groups: groups} +    end + +    test "config for list of files", %{groups: groups} do +      group = +        groups +        |> Emoji.match_extra("/emoji/custom/first_file.png") +        |> to_string() + +      assert group == "list of files" +    end + +    test "config with wildcard folder", %{groups: groups} do +      group = +        groups +        |> Emoji.match_extra("/emoji/custom/some_folder/file.png") +        |> to_string() + +      assert group == "wildcard folder" +    end + +    test "config with wildcard folder and subfolders", %{groups: groups} do +      group = +        groups +        |> Emoji.match_extra("/emoji/custom/some_folder/another_folder/file.png") +        |> to_string() + +      assert group == "wildcard folder" +    end + +    test "config with wildcard files", %{groups: groups} do +      group = +        groups +        |> Emoji.match_extra("/emoji/custom/folder/some_file.png") +        |> to_string() + +      assert group == "wildcard files" +    end + +    test "config with wildcard files and subfolders", %{groups: groups} do +      group = +        groups +        |> Emoji.match_extra("/emoji/custom/folder/another_folder/some_file.png") +        |> to_string() + +      assert group == "wildcard files" +    end + +    test "config for special file", %{groups: groups} do +      group = +        groups +        |> Emoji.match_extra("/emoji/custom/special.png") +        |> to_string() + +      assert group == "special file" +    end + +    test "no mathing returns nil", %{groups: groups} do +      group = +        groups +        |> Emoji.match_extra("/emoji/some_undefined.png") + +      refute group +    end +  end +end diff --git a/test/fixtures/httpoison_mock/emelie.atom b/test/fixtures/httpoison_mock/emelie.atom new file mode 100644 index 000000000..ddaa1c6ca --- /dev/null +++ b/test/fixtures/httpoison_mock/emelie.atom @@ -0,0 +1,306 @@ +<?xml version="1.0"?> +<feed xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:media="http://purl.org/syndication/atommedia" xmlns:ostatus="http://ostatus.org/schema/1.0" xmlns:mastodon="http://mastodon.social/schema/1.0"> +    <id>https://mastodon.social/users/emelie.atom</id> +    <title>emelie 🎨</title> +    <subtitle>23 / #Sweden / #Artist / #Equestrian / #GameDev + +If I ain't spending time with my pets, I'm probably drawing. 🐴 🐱 🐰</subtitle> +    <updated>2019-02-04T20:22:19Z</updated> +    <logo>https://files.mastodon.social/accounts/avatars/000/015/657/original/e7163f98280da1a4.png</logo> +    <author> +        <id>https://mastodon.social/users/emelie</id> +        <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type> +        <uri>https://mastodon.social/users/emelie</uri> +        <name>emelie</name> +        <email>emelie@mastodon.social</email> +        <summary type="html"><p>23 / <a href="https://mastodon.social/tags/sweden" class="mention hashtag" rel="tag">#<span>Sweden</span></a> / <a href="https://mastodon.social/tags/artist" class="mention hashtag" rel="tag">#<span>Artist</span></a> / <a href="https://mastodon.social/tags/equestrian" class="mention hashtag" rel="tag">#<span>Equestrian</span></a> / <a href="https://mastodon.social/tags/gamedev" class="mention hashtag" rel="tag">#<span>GameDev</span></a></p><p>If I ain&apos;t spending time with my pets, I&apos;m probably drawing. 🐴 🐱 🐰</p></summary> +        <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie"/> +        <link rel="avatar" type="image/png" media:width="120" media:height="120" href="https://files.mastodon.social/accounts/avatars/000/015/657/original/e7163f98280da1a4.png"/> +        <link rel="header" type="image/png" media:width="700" media:height="335" href="https://files.mastodon.social/accounts/headers/000/015/657/original/847f331f3dd9e38b.png"/> +        <poco:preferredUsername>emelie</poco:preferredUsername> +        <poco:displayName>emelie 🎨</poco:displayName> +        <poco:note>23 / #Sweden / #Artist / #Equestrian / #GameDev + +If I ain't spending time with my pets, I'm probably drawing. 🐴 🐱 🐰</poco:note> +        <mastodon:scope>public</mastodon:scope> +    </author> +    <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie"/> +    <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie.atom"/> +    <link rel="hub" href="https://mastodon.social/api/push"/> +    <link rel="salmon" href="https://mastodon.social/api/salmon/15657"/> +    <entry> +        <id>https://mastodon.social/users/emelie/statuses/101850331907006641</id> +        <published>2019-04-01T09:58:50Z</published> +        <updated>2019-04-01T09:58:50Z</updated> +        <title>New status by emelie</title> +        <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type> +        <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb> +        <link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101850331907006641"/> +        <content type="html" xml:lang="en"><p>Me: I&apos;m going to make this vital change to my world building in the morning, no way I&apos;ll forget this, it&apos;s too big of a deal<br />Also me: forgets</p></content> +        <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/> +        <mastodon:scope>public</mastodon:scope> +        <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101850331907006641"/> +        <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17854598.atom"/> +        <ostatus:conversation ref="tag:mastodon.social,2019-04-01:objectId=94383214:objectType=Conversation"/> +    </entry> +    <entry> +        <id>https://mastodon.social/users/emelie/statuses/101849626603073336</id> +        <published>2019-04-01T06:59:28Z</published> +        <updated>2019-04-01T06:59:28Z</updated> +        <title>New status by emelie</title> +        <activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type> +        <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb> +        <link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101849626603073336"/> +        <content type="html" xml:lang="sv"><p><span class="h-card"><a href="https://mastodon.social/@Fergant" class="u-url mention">@<span>Fergant</span></a></span> Dom är i stort sett religiös skrift vid det här laget 👏👏</p><p>har dock bara läst svenska översättningen, kanske är dags att jag läser dom på engelska</p></content> +        <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="https://mastodon.social/users/Fergant"/> +        <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/> +        <mastodon:scope>public</mastodon:scope> +        <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101849626603073336"/> +        <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17852590.atom"/> +        <thr:in-reply-to ref="https://mastodon.social/users/Fergant/statuses/101849606513357387" href="https://mastodon.social/@Fergant/101849606513357387"/> +        <ostatus:conversation ref="tag:mastodon.social,2019-04-01:objectId=94362529:objectType=Conversation"/> +    </entry> +    <entry> +        <id>https://mastodon.social/users/emelie/statuses/101849580030237068</id> +        <published>2019-04-01T06:47:37Z</published> +        <updated>2019-04-01T06:47:37Z</updated> +        <title>New status by emelie</title> +        <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type> +        <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb> +        <link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101849580030237068"/> +        <content type="html" xml:lang="en"><p>What&apos;s you people&apos;s favourite fantasy books? Give me some hot tips 🌞</p></content> +        <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/> +        <mastodon:scope>public</mastodon:scope> +        <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101849580030237068"/> +        <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17852464.atom"/> +        <ostatus:conversation ref="tag:mastodon.social,2019-04-01:objectId=94362529:objectType=Conversation"/> +    </entry> +    <entry> +        <id>https://mastodon.social/users/emelie/statuses/101849550599949363</id> +        <published>2019-04-01T06:40:08Z</published> +        <updated>2019-04-01T06:40:08Z</updated> +        <title>New status by emelie</title> +        <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type> +        <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb> +        <link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101849550599949363"/> +        <content type="html" xml:lang="en"><p>Stick them legs out 💃 <a href="https://mastodon.social/tags/mastocats" class="mention hashtag" rel="tag">#<span>mastocats</span></a></p></content> +        <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/> +        <category term="mastocats"/> +        <link rel="enclosure" type="image/jpeg" length="516384" href="https://files.mastodon.social/media_attachments/files/013/051/707/original/125a310abe9a34aa.jpeg"/> +        <mastodon:scope>public</mastodon:scope> +        <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101849550599949363"/> +        <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17852407.atom"/> +        <ostatus:conversation ref="tag:mastodon.social,2019-04-01:objectId=94361580:objectType=Conversation"/> +    </entry> +    <entry> +        <id>https://mastodon.social/users/emelie/statuses/101849191533152720</id> +        <published>2019-04-01T05:08:49Z</published> +        <updated>2019-04-01T05:08:49Z</updated> +        <title>New status by emelie</title> +        <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type> +        <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb> +        <link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101849191533152720"/> +        <content type="html" xml:lang="en"><p>long 🐱 <a href="https://mastodon.social/tags/mastocats" class="mention hashtag" rel="tag">#<span>mastocats</span></a></p></content> +        <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/> +        <category term="mastocats"/> +        <link rel="enclosure" type="image/jpeg" length="305208" href="https://files.mastodon.social/media_attachments/files/013/049/940/original/f2dbbfe7de3a17d2.jpeg"/> +        <mastodon:scope>public</mastodon:scope> +        <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101849191533152720"/> +        <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17851663.atom"/> +        <ostatus:conversation ref="tag:mastodon.social,2019-04-01:objectId=94351141:objectType=Conversation"/> +    </entry> +    <entry> +        <id>https://mastodon.social/users/emelie/statuses/101849165031453009</id> +        <published>2019-04-01T05:02:05Z</published> +        <updated>2019-04-01T05:02:05Z</updated> +        <title>New status by emelie</title> +        <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type> +        <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb> +        <link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101849165031453009"/> +        <content type="html" xml:lang="en"><p>You gotta take whatever bellyrubbing opportunity you can get before she changes her mind 🦁 <a href="https://mastodon.social/tags/mastocats" class="mention hashtag" rel="tag">#<span>mastocats</span></a></p></content> +        <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/> +        <category term="mastocats"/> +        <link rel="enclosure" type="video/mp4" length="9838915" href="https://files.mastodon.social/media_attachments/files/013/049/816/original/e7831178a5e0d6d4.mp4"/> +        <mastodon:scope>public</mastodon:scope> +        <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101849165031453009"/> +        <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17851558.atom"/> +        <ostatus:conversation ref="tag:mastodon.social,2019-04-01:objectId=94350309:objectType=Conversation"/> +    </entry> +    <entry> +        <id>https://mastodon.social/users/emelie/statuses/101846512530748693</id> +        <published>2019-03-31T17:47:31Z</published> +        <updated>2019-03-31T17:47:31Z</updated> +        <title>New status by emelie</title> +        <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type> +        <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb> +        <link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101846512530748693"/> +        <content type="html" xml:lang="en"><p>Hello look at this boy having a decent haircut for once <a href="https://mastodon.social/tags/mastohorses" class="mention hashtag" rel="tag">#<span>mastohorses</span></a> <a href="https://mastodon.social/tags/equestrian" class="mention hashtag" rel="tag">#<span>equestrian</span></a></p></content> +        <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/> +        <category term="equestrian"/> +        <category term="mastohorses"/> +        <link rel="enclosure" type="image/jpeg" length="461632" href="https://files.mastodon.social/media_attachments/files/013/033/387/original/301e8ab668cd61d2.jpeg"/> +        <mastodon:scope>public</mastodon:scope> +        <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101846512530748693"/> +        <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17842424.atom"/> +        <ostatus:conversation ref="tag:mastodon.social,2019-03-31:objectId=94256415:objectType=Conversation"/> +    </entry> +    <entry> +        <id>https://mastodon.social/users/emelie/statuses/101846181093805500</id> +        <published>2019-03-31T16:23:14Z</published> +        <updated>2019-03-31T16:23:14Z</updated> +        <title>New status by emelie</title> +        <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type> +        <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb> +        <link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101846181093805500"/> +        <content type="html" xml:lang="en"><p>Sorry did I disturb the who-is-the-longest-cat competition ?  <a href="https://mastodon.social/tags/mastocats" class="mention hashtag" rel="tag">#<span>mastocats</span></a></p></content> +        <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/> +        <category term="mastocats"/> +        <link rel="enclosure" type="image/jpeg" length="211384" href="https://files.mastodon.social/media_attachments/files/013/030/725/original/5b4886730cbbd25c.jpeg"/> +        <mastodon:scope>public</mastodon:scope> +        <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101846181093805500"/> +        <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17841108.atom"/> +        <ostatus:conversation ref="tag:mastodon.social,2019-03-31:objectId=94245239:objectType=Conversation"/> +    </entry> +    <entry> +        <id>https://mastodon.social/users/emelie/statuses/101845897513133849</id> +        <published>2019-03-31T15:11:07Z</published> +        <updated>2019-03-31T15:11:07Z</updated> +        <title>New status by emelie</title> +        <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type> +        <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb> +        <link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101845897513133849"/> +        <summary xml:lang="en">more earthsea ramblings</summary> +        <content type="html" xml:lang="en"><p>I&apos;m re-watching Tales from Earthsea for the first time since I read the books, and that Therru doesn&apos;t squash Cob like a spider, as Orm Embar did is a wasted opportunity tbh</p></content> +        <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/> +        <mastodon:scope>public</mastodon:scope> +        <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101845897513133849"/> +        <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17840088.atom"/> +        <ostatus:conversation ref="tag:mastodon.social,2019-03-31:objectId=94232455:objectType=Conversation"/> +    </entry> +    <entry> +        <id>https://mastodon.social/users/emelie/statuses/101841219051533307</id> +        <published>2019-03-30T19:21:19Z</published> +        <updated>2019-03-30T19:21:19Z</updated> +        <title>New status by emelie</title> +        <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type> +        <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb> +        <link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101841219051533307"/> +        <content type="html" xml:lang="en"><p>I gave my cats some mackerel and they ate it all in 0.3 seconds, and now they won&apos;t stop meowing for more, and I&apos;m tired plz shut up</p></content> +        <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/> +        <mastodon:scope>public</mastodon:scope> +        <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101841219051533307"/> +        <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17826587.atom"/> +        <ostatus:conversation ref="tag:mastodon.social,2019-03-30:objectId=94075000:objectType=Conversation"/> +    </entry> +    <entry> +        <id>https://mastodon.social/users/emelie/statuses/101839949762341381</id> +        <published>2019-03-30T13:58:31Z</published> +        <updated>2019-03-30T13:58:31Z</updated> +        <title>New status by emelie</title> +        <activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type> +        <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb> +        <link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101839949762341381"/> +        <content type="html" xml:lang="en"><p>yet I&apos;m  confused about this american dude with a gun, like the heck r ya doin in mah ghibli</p></content> +        <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/> +        <mastodon:scope>public</mastodon:scope> +        <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101839949762341381"/> +        <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17821757.atom"/> +        <thr:in-reply-to ref="https://mastodon.social/users/emelie/statuses/101839928677863590" href="https://mastodon.social/@emelie/101839928677863590"/> +        <ostatus:conversation ref="tag:mastodon.social,2019-03-30:objectId=94026360:objectType=Conversation"/> +    </entry> +    <entry> +        <id>https://mastodon.social/users/emelie/statuses/101839928677863590</id> +        <published>2019-03-30T13:53:09Z</published> +        <updated>2019-03-30T13:53:09Z</updated> +        <title>New status by emelie</title> +        <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type> +        <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb> +        <link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101839928677863590"/> +        <content type="html" xml:lang="en"><p>2 hours into Ni no Kuni 2 and I&apos;ve already sold my soul to this game</p></content> +        <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/> +        <mastodon:scope>public</mastodon:scope> +        <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101839928677863590"/> +        <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17821713.atom"/> +        <ostatus:conversation ref="tag:mastodon.social,2019-03-30:objectId=94026360:objectType=Conversation"/> +    </entry> +    <entry> +        <id>https://mastodon.social/users/emelie/statuses/101836329521599438</id> +        <published>2019-03-29T22:37:51Z</published> +        <updated>2019-03-29T22:37:51Z</updated> +        <title>New status by emelie</title> +        <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type> +        <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb> +        <link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101836329521599438"/> +        <content type="html" xml:lang="en"><p>Pippi Longstocking the original one-punch /man</p></content> +        <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/> +        <mastodon:scope>public</mastodon:scope> +        <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101836329521599438"/> +        <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17811608.atom"/> +        <ostatus:conversation ref="tag:mastodon.social,2019-03-29:objectId=93907854:objectType=Conversation"/> +    </entry> +    <entry> +        <id>https://mastodon.social/users/emelie/statuses/101835905282948341</id> +        <published>2019-03-29T20:49:57Z</published> +        <updated>2019-03-29T20:49:57Z</updated> +        <title>New status by emelie</title> +        <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type> +        <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb> +        <link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101835905282948341"/> +        <content type="html" xml:lang="en"><p>I&apos;ve had so much wine I thought I had a 3rd brother</p></content> +        <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/> +        <mastodon:scope>public</mastodon:scope> +        <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101835905282948341"/> +        <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17809862.atom"/> +        <ostatus:conversation ref="tag:mastodon.social,2019-03-29:objectId=93892966:objectType=Conversation"/> +    </entry> +    <entry> +        <id>https://mastodon.social/users/emelie/statuses/101835878059204660</id> +        <published>2019-03-29T20:43:02Z</published> +        <updated>2019-03-29T20:43:02Z</updated> +        <title>New status by emelie</title> +        <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type> +        <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb> +        <link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101835878059204660"/> +        <content type="html" xml:lang="en"><p>ååååhhh booi</p></content> +        <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/> +        <mastodon:scope>public</mastodon:scope> +        <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101835878059204660"/> +        <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17809734.atom"/> +        <ostatus:conversation ref="tag:mastodon.social,2019-03-29:objectId=93892010:objectType=Conversation"/> +    </entry> +    <entry> +        <id>https://mastodon.social/users/emelie/statuses/101835848050598939</id> +        <published>2019-03-29T20:35:24Z</published> +        <updated>2019-03-29T20:35:24Z</updated> +        <title>New status by emelie</title> +        <activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type> +        <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb> +        <link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101835848050598939"/> +        <content type="html" xml:lang="en"><p><span class="h-card"><a href="https://thraeryn.net/@thraeryn" class="u-url mention">@<span>thraeryn</span></a></span> if I spent 1 hour and a half watching this monstrosity, I need to</p></content> +        <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="https://thraeryn.net/users/thraeryn"/> +        <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/> +        <mastodon:scope>public</mastodon:scope> +        <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101835848050598939"/> +        <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17809591.atom"/> +        <thr:in-reply-to ref="https://thraeryn.net/users/thraeryn/statuses/101835839202826007" href="https://thraeryn.net/@thraeryn/101835839202826007"/> +        <ostatus:conversation ref="tag:mastodon.social,2019-03-29:objectId=93888827:objectType=Conversation"/> +    </entry> +    <entry> +        <id>https://mastodon.social/users/emelie/statuses/101835823138262290</id> +        <published>2019-03-29T20:29:04Z</published> +        <updated>2019-03-29T20:29:04Z</updated> +        <title>New status by emelie</title> +        <activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type> +        <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb> +        <link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101835823138262290"/> +        <summary xml:lang="en">medical, fluids mention</summary> +        <content type="html" xml:lang="en"><p><span class="h-card"><a href="https://icosahedron.website/@Trev" class="u-url mention">@<span>Trev</span></a></span> *hugs* ✨</p></content> +        <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="https://icosahedron.website/users/Trev"/> +        <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/> +        <mastodon:scope>public</mastodon:scope> +        <link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101835823138262290"/> +        <link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17809468.atom"/> +        <thr:in-reply-to ref="https://icosahedron.website/users/Trev/statuses/101835812250051801" href="https://icosahedron.website/@Trev/101835812250051801"/> +        <ostatus:conversation ref="tag:icosahedron.website,2019-03-29:objectId=12220882:objectType=Conversation"/> +    </entry> +</feed> diff --git a/test/fixtures/httpoison_mock/status.emelie.json b/test/fixtures/httpoison_mock/status.emelie.json new file mode 100644 index 000000000..4aada0377 --- /dev/null +++ b/test/fixtures/httpoison_mock/status.emelie.json @@ -0,0 +1,64 @@ +{ +    "@context": [ +        "https://www.w3.org/ns/activitystreams", +        { +            "ostatus": "http://ostatus.org#", +            "atomUri": "ostatus:atomUri", +            "inReplyToAtomUri": "ostatus:inReplyToAtomUri", +            "conversation": "ostatus:conversation", +            "sensitive": "as:sensitive", +            "Hashtag": "as:Hashtag", +            "toot": "http://joinmastodon.org/ns#", +            "Emoji": "toot:Emoji", +            "focalPoint": { +                "@container": "@list", +                "@id": "toot:focalPoint" +            } +        } +    ], +    "id": "https://mastodon.social/users/emelie/statuses/101849165031453009", +    "type": "Note", +    "summary": null, +    "inReplyTo": null, +    "published": "2019-04-01T05:02:05Z", +    "url": "https://mastodon.social/@emelie/101849165031453009", +    "attributedTo": "https://mastodon.social/users/emelie", +    "to": [ +        "https://www.w3.org/ns/activitystreams#Public" +    ], +    "cc": [ +        "https://mastodon.social/users/emelie/followers" +    ], +    "sensitive": false, +    "atomUri": "https://mastodon.social/users/emelie/statuses/101849165031453009", +    "inReplyToAtomUri": null, +    "conversation": "tag:mastodon.social,2019-04-01:objectId=94350309:objectType=Conversation", +    "content": "<p>You gotta take whatever bellyrubbing opportunity you can get before she changes her mind 🦁 <a href=\"https://mastodon.social/tags/mastocats\" class=\"mention hashtag\" rel=\"tag\">#<span>mastocats</span></a></p>", +    "contentMap": { +        "en": "<p>You gotta take whatever bellyrubbing opportunity you can get before she changes her mind 🦁 <a href=\"https://mastodon.social/tags/mastocats\" class=\"mention hashtag\" rel=\"tag\">#<span>mastocats</span></a></p>" +    }, +    "attachment": [ +        { +            "type": "Document", +            "mediaType": "video/mp4", +            "url": "https://files.mastodon.social/media_attachments/files/013/049/816/original/e7831178a5e0d6d4.mp4", +            "name": null +        } +    ], +    "tag": [ +        { +            "type": "Hashtag", +            "href": "https://mastodon.social/tags/mastocats", +            "name": "#mastocats" +        } +    ], +    "replies": { +        "id": "https://mastodon.social/users/emelie/statuses/101849165031453009/replies", +        "type": "Collection", +        "first": { +            "type": "CollectionPage", +            "partOf": "https://mastodon.social/users/emelie/statuses/101849165031453009/replies", +            "items": [] +        } +    } +} diff --git a/test/fixtures/httpoison_mock/webfinger_emelie.json b/test/fixtures/httpoison_mock/webfinger_emelie.json new file mode 100644 index 000000000..0b61cb618 --- /dev/null +++ b/test/fixtures/httpoison_mock/webfinger_emelie.json @@ -0,0 +1,36 @@ +{ +    "aliases": [ +        "https://mastodon.social/@emelie", +        "https://mastodon.social/users/emelie" +    ], +    "links": [ +        { +            "href": "https://mastodon.social/@emelie", +            "rel": "http://webfinger.net/rel/profile-page", +            "type": "text/html" +        }, +        { +            "href": "https://mastodon.social/users/emelie.atom", +            "rel": "http://schemas.google.com/g/2010#updates-from", +            "type": "application/atom+xml" +        }, +        { +            "href": "https://mastodon.social/users/emelie", +            "rel": "self", +            "type": "application/activity+json" +        }, +        { +            "href": "https://mastodon.social/api/salmon/15657", +            "rel": "salmon" +        }, +        { +            "href": "data:application/magic-public-key,RSA.u3CWs1oAJPE3ZJ9sj6Ut_Mu-mTE7MOijsQc8_6c73XVVuhIEomiozJIH7l8a7S1n5SYL4UuiwcubSOi7u1bbGpYnp5TYhN-Cxvq_P80V4_ncNIPSQzS49it7nSLeG5pA21lGPDA44huquES1un6p9gSmbTwngVX9oe4MYuUeh0Z7vijjU13Llz1cRq_ZgPQPgfz-2NJf-VeXnvyDZDYxZPVBBlrMl3VoGbu0M5L8SjY35559KCZ3woIvqRolcoHXfgvJMdPcJgSZVYxlCw3dA95q9jQcn6s87CPSUs7bmYEQCrDVn5m5NER5TzwBmP4cgJl9AaDVWQtRd4jFZNTxlQ==.AQAB", +            "rel": "magic-public-key" +        }, +        { +            "rel": "http://ostatus.org/schema/1.0/subscribe", +            "template": "https://mastodon.social/authorize_interaction?uri={uri}" +        } +    ], +    "subject": "acct:emelie@mastodon.social" +} diff --git a/test/fixtures/image.jpg b/test/fixtures/image.jpg Binary files differindex 09834bb5c..edff6246b 100644 --- a/test/fixtures/image.jpg +++ b/test/fixtures/image.jpg diff --git a/test/fixtures/lambadalambda.json b/test/fixtures/lambadalambda.json new file mode 100644 index 000000000..1f09fb591 --- /dev/null +++ b/test/fixtures/lambadalambda.json @@ -0,0 +1,64 @@ +{ +  "@context": [ +    "https://www.w3.org/ns/activitystreams", +    "https://w3id.org/security/v1", +    { +      "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", +      "toot": "http://joinmastodon.org/ns#", +      "featured": { +        "@id": "toot:featured", +        "@type": "@id" +      }, +      "alsoKnownAs": { +        "@id": "as:alsoKnownAs", +        "@type": "@id" +      }, +      "movedTo": { +        "@id": "as:movedTo", +        "@type": "@id" +      }, +      "schema": "http://schema.org#", +      "PropertyValue": "schema:PropertyValue", +      "value": "schema:value", +      "Hashtag": "as:Hashtag", +      "Emoji": "toot:Emoji", +      "IdentityProof": "toot:IdentityProof", +      "focalPoint": { +        "@container": "@list", +        "@id": "toot:focalPoint" +      } +    } +  ], +  "id": "https://mastodon.social/users/lambadalambda", +  "type": "Person", +  "following": "https://mastodon.social/users/lambadalambda/following", +  "followers": "https://mastodon.social/users/lambadalambda/followers", +  "inbox": "https://mastodon.social/users/lambadalambda/inbox", +  "outbox": "https://mastodon.social/users/lambadalambda/outbox", +  "featured": "https://mastodon.social/users/lambadalambda/collections/featured", +  "preferredUsername": "lambadalambda", +  "name": "Critical Value", +  "summary": "\u003cp\u003e\u003c/p\u003e", +  "url": "https://mastodon.social/@lambadalambda", +  "manuallyApprovesFollowers": false, +  "publicKey": { +    "id": "https://mastodon.social/users/lambadalambda#main-key", +    "owner": "https://mastodon.social/users/lambadalambda", +    "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw0P/Tq4gb4G/QVuMGbJo\nC/AfMNcv+m7NfrlOwkVzcU47jgESuYI4UtJayissCdBycHUnfVUd9qol+eznSODz\nCJhfJloqEIC+aSnuEPGA0POtWad6DU0E6/Ho5zQn5WAWUwbRQqowbrsm/GHo2+3v\neR5jGenwA6sYhINg/c3QQbksyV0uJ20Umyx88w8+TJuv53twOfmyDWuYNoQ3y5cc\nHKOZcLHxYOhvwg3PFaGfFHMFiNmF40dTXt9K96r7sbzc44iLD+VphbMPJEjkMuf8\nPGEFOBzy8pm3wJZw2v32RNW2VESwMYyqDzwHXGSq1a73cS7hEnc79gXlELsK04L9\nQQIDAQAB\n-----END PUBLIC KEY-----\n" +  }, +  "tag": [], +  "attachment": [], +  "endpoints": { +    "sharedInbox": "https://mastodon.social/inbox" +  }, +  "icon": { +    "type": "Image", +    "mediaType": "image/gif", +    "url": "https://files.mastodon.social/accounts/avatars/000/000/264/original/1429214160519.gif" +  }, +  "image": { +    "type": "Image", +    "mediaType": "image/gif", +    "url": "https://files.mastodon.social/accounts/headers/000/000/264/original/28b26104f83747d2.gif" +  } +} diff --git a/test/fixtures/rel_me_anchor.html b/test/fixtures/rel_me_anchor.html new file mode 100644 index 000000000..5abcce129 --- /dev/null +++ b/test/fixtures/rel_me_anchor.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html> +	<head> +		<meta charset="utf-8"/> +		<title>Blog</title> +	</head> +	<body> +	<article> +		<h1>Lorem ipsum</h1> +		<p>Lorem ipsum dolor sit ameph, …</p> +		<a rel="me" href="https://social.example.org/users/lain">lain’s account</a> +	</article> +	</body> +</html> diff --git a/test/fixtures/rel_me_anchor_nofollow.html b/test/fixtures/rel_me_anchor_nofollow.html new file mode 100644 index 000000000..c856f0091 --- /dev/null +++ b/test/fixtures/rel_me_anchor_nofollow.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html> +	<head> +		<meta charset="utf-8"/> +		<title>Blog</title> +	</head> +	<body> +	<article> +		<h1>Lorem ipsum</h1> +		<p>Lorem ipsum dolor sit ameph, …</p> +		<a rel="me nofollow" href="https://social.example.org/users/lain">lain’s account</a> +	</article> +	</body> +</html> diff --git a/test/fixtures/rel_me_link.html b/test/fixtures/rel_me_link.html new file mode 100644 index 000000000..b9ff18f6e --- /dev/null +++ b/test/fixtures/rel_me_link.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html> +	<head> +		<meta charset="utf-8"/> +		<title>Blog</title> +		<link rel="me" href="https://social.example.org/users/lain"/> +	</head> +	<body> +	<article> +		<h1>Lorem ipsum</h1> +		<p>Lorem ipsum dolor sit ameph, …</p> +	</article> +	</body> +</html> diff --git a/test/fixtures/rel_me_null.html b/test/fixtures/rel_me_null.html new file mode 100644 index 000000000..5ab5f10c1 --- /dev/null +++ b/test/fixtures/rel_me_null.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html> +	<head> +		<meta charset="utf-8"/> +		<title>Blog</title> +	</head> +	<body> +	<article> +		<h1>Lorem ipsum</h1> +		<p>Lorem ipsum dolor sit ameph, …</p> +		<a rel="nofollow" href="https://social.example.org/users/lain">lain’s account</a> +	</article> +	</body> +</html> diff --git a/test/fixtures/rich_media/malformed-data.html b/test/fixtures/rich_media/malformed-data.html new file mode 100644 index 000000000..dec628c16 --- /dev/null +++ b/test/fixtures/rich_media/malformed-data.html @@ -0,0 +1,4874 @@ +
 +
 +
 +
 +
 +
 +
 +
 +<!DOCTYPE html>
 +<html lang="pl">
 +<head>
 +	
 +
 +
 +
 +
 +<!-- canonical_start -->
 +
 +    <link rel="canonical" href="http://wyborcza.biz/biznes/7,147743,24417936,pomysl-na-biznes-chusta-ktora-chroni-przed-smogiem.html"/>
 +
 +
 +<!-- canonical_end -->
 + +<!--10351348, [ /fix/modules/canonical.jsp ], canonicalModule--> +
 +	
 +
 +<script>
 +	var activeSubscription = false;
 +</script>
 +
 +<!-- playerInfoModule v1.0 -->
 +
 +
 +
 +
 +	<META NAME="ROBOTS" CONTENT="NOARCHIVE">
 +
 +
 +
 +
 +<!-- robotsModule v1.0 -->
 +
 +
 +<title>Pomys na biznes: chusta, ktra chroni przed smogiem</title>
 +<meta charset="ISO-8859-2">
 +<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
 +<link rel="shortcut icon" href="http://bi.gazeta.pl/im/5/7001/m7001335.gif">
 +
 +
 +
 +<link rel="apple-touch-icon-precomposed" sizes="144x144" href="http://static.im-g.pl/aliasy/foto/wyborcza/touch-icon-144x144.png">
 +<link rel="apple-touch-icon-precomposed" sizes="114x114" href="http://static.im-g.pl/aliasy/foto/wyborcza/touch-icon-114x114.png">
 +<link rel="apple-touch-icon-precomposed" sizes="72x72" href="http://static.im-g.pl/aliasy/foto/wyborcza/touch-icon-72x72.png">
 +<link rel="apple-touch-icon-precomposed" href="http://static.im-g.pl/aliasy/foto/wyborcza/touch-icon-72x72.png">
 +
 +
 +<!-- titleCharsetModule v1.0 -->
 +
 +
 +
 +
 +
 +	
 +	
 +        <!-- style preloader-test -->
 +		<style> +img[data-src]:not(.loaded){position:absolute !important;top:0 !important;left:0 !important;display:block !important;width:0 !important;height:0 !important;visibility:hidden !important;font-size:0 !important}.preloaderContainer{position:relative;z-index:1;display:block;width:100%;height:100%;min-height:56px;background:#fff url("data:image/gif;base64,R0lGODlhJgA4APEDAP+AgP8AAP///wAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFDQADACwAAAAAJgA4AAACcIyPicDt75RUsMKJjd0sY755E2iJElmZFPqoCdu6BxzJGt3ZAZ7bPKDb8YI/4lBXRB59S1mSiTNGlVMoTXqlZq0wbFcLE4jH5LL5jE6r1+y2+w2Py+f0uv2Oz+v3/L7/DxgoOEhYaHiImKi4yNjYVwAAIfkEBQ0AAgAsAAAAACYAHAAAAkaEj4nB7e+UVLDCiY3dLGO+eRNoiRJZmRT6qAnbugccyRrd2QCe23yg2/GCP+JQV0QefUtZkokzRpVTKE16pWatMGxXCysAACH5BAUNAAAALAAAAAATABwAAAIVlI+py+0Po5y02ouz3rz7D4biSCIFACH5BAUNAAEALBMAHAATABwAAAIVhI+py+0Po5y02ouz3rz7D4biSCIFACH5BAUNAAIALBMAAAATADgAAAIvhI+py+0Po5y02ouz3rz7D4biSCLBiabqyrbuC8fyTNf2jef6zvf+DwwKh8RiqgAAIfkEBQ0AAAAsEwAAABMAHAAAAhWUj6nL7Q+jnLTai7PevPsPhuJIIgUAIfkEBQ0AAQAsAAAcABMAHAAAAhWEj6nL7Q+jnLTai7PevPsPhuJIIgUAIfkEBQ0AAgAsAAAcACYAHAAAAkaMj4nA7e+UVLDCiY3dLGO+eRNoiRJZmRT6qAnbugccyRrd2QGe2zyg2/GCP+JQV0QefUtZkokzRpVTKE16pWatMGxXCysAACH5BAUNAAAALBMAHAATABwAAAIVlI+py+0Po5y02ouz3rz7D4biSCIFACH5BAUNAAEALAAAAAATABwAAAIVhI+py+0Po5y02ouz3rz7D4biSCIFACH5BAUNAAIALAAAAAATADgAAAIvjI+py+0Po5y02ouz3rz7D4biSCLAiabqyrbuC8fyTNf2jef6zvf+DwwKh8RiqgAAIfkEBQ0AAAAsAAAcABMAHAAAAhWUj6nL7Q+jnLTai7PevPsPhuJIIgUAOw==") center center no-repeat} + +</style>
 +        <!-- /style preloader-test -->
 +	
 +
 +
 +
 +
 +	
 +	
 +        <!-- head script -->
 +		<script>function checkAdsElements(){if(void 0!==this.dfpArrLab&&this.dfpArrLab instanceof Array){for(var a=0;a<this.dfpArrLab.length;){var b=document.getElementById(this.dfpArrLab[a]);if(b){var c=b.clientHeight,d=b.querySelector("span.banLabel");c>40&&d.setAttribute("style","")}a++}return!0}return!1}function putBan(a){return!0}var wyborcza_pl=wyborcza_pl||{},gazeta_pl=gazeta_pl||{},_gaq=_gaq||[];this.dfpArrLab=["000-MAINBOARD","087-ADBOARD-A","087-ADBOARD-B","087-ADBOARD-C","087-ADBOARD-D","107-MAINBOARD-MOBI","150-ADBOARD-A-MOBI","150-ADBOARD-B-MOBI","150-ADBOARD-C-MOBI","150-ADBOARD-D-MOBI","003-RECTANGLE","035-RECTANGLE-BTF","042-FOOTBOARD","104-RECTANGLE-MOBI","108-FOOTBOARD-MOBI","ADBOARD-B","FOOTBOARDA"],document.addEventListener("DOMContentLoaded",function(a){setTimeout(function(){checkAdsElements()},1)}),wyborcza_pl.PlayerConfig={isMuted:!0},gazeta_pl.parseURI=function(){var a={},b=location.href.split(/\?/).pop().split(/&/),c=0;for(c=0;c<b.length;c++)b[c]=b[c].split(/=/),a[b[c].shift()]=b[c].join("=");return a},function(){var a=gazeta_pl.Player=gazeta_pl.Player||{resizeWrappers:["body","body > .main","#col_left","#col_right",".colLeft",".colRight"],jsonldMounted:0,init:function(){a.checkPageVisibilityPage(),a.initMessages(),a.initResizeHandler(),a.initShortcuts()},initMessages:function(){a.addEvent(window,"message",a.receiveMessage)},receiveMessage:function(b){var c=b.data;if(b.origin.match(/video\.gazeta\.pl$/)){try{c=JSON.parse(c)}catch(i){}if(c)switch(c.action){case"playerLoad":a.sendMessage(b.source,b.origin,{action:"playerInit",params:{mute:a.getMuteState()}}),a.resizeHandler();break;case"getAdParams":var d=window.AG&&window.AG.rodoAccepted||0,e={action:"setAdParams",referrer:document.referrer,rodoStatus:d};window.nobanner?e.nobanner=!0:"dfpParams"in window&&dfpParams&&dfpParams.video&&(e.url={preroll:dfpParams.video.preroll,postroll:dfpParams.video.postroll,inskin:dfpParams.video.skin,branding:dfpParams.video.skin,pausead:dfpParams.video.pausead,overlay:dfpParams.video.overlay}),a.sendMessage(b.source,b.origin,e);break;case"watchViewport":a.checkPlayerInViewport(b);break;case"resizeRequest":a.stretchSpace(b,c.width,c.height,function(c,d){a.sendMessage(b.source,b.origin,{action:"resizeDone",width:c,height:d})});break;case"hidePlayer":var f=a.findPlayerIframe(b);f&&(f.style.cssText+="display: none; visibility: hidden");break;case"getPlayerParams":var f=a.findPlayerIframe(b);a.sendMessage(b.source,b.origin,{action:"playerParams",pageUrl:document.URL,playerUrl:f?f.src:""});break;case"initFloatingPlayer":if(!window.jQuery)return;!a.FloatingPlayer.ready&&a.FloatingPlayer.isDesktopBrowser()&&!a.FloatingPlayer.isSafariBrowser()&&a.FloatingPlayer.isEmbededOnArticlePage()&&a.FloatingPlayer.init(),a.sendMessage(b.source,b.origin,{action:"setFloatingCapabilities",canFloat:!!a.FloatingPlayer.isFloatingPlayer(b.source)&&a.FloatingPlayer.ready});break;case"canPause":a.FloatingPlayer.ready&&a.FloatingPlayer.isFloatingPlayer(b.source)&&a.FloatingPlayer.closeButtonHandler(b.origin,c.canPauseState,c.playState);break;case"pushDataToDataLayer":a.pushDataToDataLayer(c.data);break;case"mediaInfo":if(!/\/1,|\/7,/.test(window.location.href)&&c.jsonld&&a.jsonldMounted<1){var g=document.querySelector("body"),h=document.createElement("script");h.setAttribute("type","application/ld+json"),h.innerHTML=JSON.stringify(c.jsonld),g.appendChild(h),a.jsonldMounted++}}}},playersInViewport:[],actualPlayed:null,sendMessage:function(a,b,c){a.postMessage(JSON.stringify(c),b)},stretchSpace:function(b,c,d,e){if(c){var f=a.findPlayerIframe(b);f&&(c&&(f.width=c),d&&(f.height=d),e(f.width,f.height))}},checkPlayerInViewport:function(b){if(b){new IntersectionObserver(function(c){c[0].intersectionRatio>=.5?a.enteredViewport(b):a.leavedViewport(b)},{threshold:[.5]}).observe(a.findPlayerIframe(b))}},checkPageVisibilityPage:function(){document.addEventListener("visibilitychange",function(){a.manageActivePlayer()})},manageActivePlayer:function(b,c){var d="visible"==document.visibilityState;if(!(!d||a.FloatingPlayer.ready&&a.actualPlayed)){var e=a.playersInViewport.length&&d?"enteredViewport":"leavedViewport";b=a.playersInViewport[0]?a.playersInViewport[0].source:b,c=a.playersInViewport[0]?a.playersInViewport[0].origin:c,b&&c&&(a.actualPlayed&&!gazeta_pl.playerStopsOutsideViewport&&a.actualPlayed[0]!=b&&a.sendMessage(a.actualPlayed[0],a.actualPlayed[1],{action:"leavedViewport"}),a.sendMessage(b,c,{action:e}),a.actualPlayed=[b,c])}},enteredViewport:function(b){b.origin,b.source;if(!gazeta_pl.playerStopsOutsideViewport&&!a.FloatingPlayer.ready)for(var c=0,d=a.playersInViewport.length;c<d;c++)a.sendMessage(a.playersInViewport[c].source,a.playersInViewport[c].origin,{action:"leavedViewport"});a.playersInViewport.push({source:b.source,origin:b.origin}),a.manageActivePlayer()},leavedViewport:function(b){for(var c=b.origin,d=b.source,e=(a.playersInViewport.length,a.playersInViewport.length-1);e>=0;e-=1)a.playersInViewport[e]&&a.playersInViewport[e].source==d&&a.playersInViewport.splice(e,1);(gazeta_pl.playerStopsOutsideViewport||a.playersInViewport.length)&&(a.FloatingPlayer.ready||a.manageActivePlayer(d,c))},initResizeHandler:function(){a.resizeHandler(),a.addEvent(window,"onorientationchange"in window?"orientationchange":"resize",a.resizeHandler)},resizeHandler:function(){clearTimeout(a.timeout),clearTimeout(a.fallbackTimeout),a.timeout=a.resizePlayer(50),a.fallbackTimeout=a.resizePlayer(500)},resizePlayer:function(b){var c=a.findPlayerIframes();return setTimeout(function(){for(var b=0;b<c.length;b++){var d=c[b],e=c[b].parentNode,f=980;e.getAttribute("data-pjs")&&(e.style.cssText="width: auto; height: auto",e=e.parentNode),f=Math.min(f,e.offsetWidth||f),a.resizeWrappers.forEach(function(a){document.querySelector(a)&&document.querySelector(a).querySelector('[src="'+d.src+'"]')&&(f=Math.min(f,document.querySelector(a).offsetWidth||f))}),f!=d.offsetWidth&&c[b].contentWindow.postMessage('{"action": "resize", "width": '+f+"}","*")}},b)},initShortcuts:function(){a.addEvent(window,"keydown",a.handleKeydown)},handleKeydown:function(b){var c="";b.altKey&&38==b.which?c="unmute":b.altKey&&40==b.which&&(c="mute"),c&&a.findPlayerIframes().forEach(function(b){a.sendMessage(b.contentWindow,"*",{action:c})})},addEvent:function(a,b,c){a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent&&a.attachEvent("on"+b,function(){c.call(a,window.event)})},findPlayerIframes:function(){return Array.prototype.filter.call(document.getElementsByTagName("iframe"),function(a){return a.src&&a.src.match(/www\.gazeta\.tv\/plej\/19,/)})},findPlayerIframe:function(b){for(var c=a.findPlayerIframes(),d=0,e=c.length;d<e;d++)if(c[d].contentWindow===b.source)return c[d];return null},pushDataToDataLayer:function(a){"undefined"!=typeof dataLayer&&dataLayer.push(a)},getMuteState:function(){return!!(gazeta_pl.playerMute||a.isAutoplayMutedVideo()||void 0!==wyborcza_pl&&wyborcza_pl.PlayerConfig&&wyborcza_pl.PlayerConfig.isMuted)},isAutoplayMutedVideo:function(){var a=navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);return!!a&&parseInt(a[2],10)>=66},FloatingPlayer:{ready:!1,init:function(){var a,b,c,d,e,f,g,h=this;return a=$(window),b=$("body"),c=b.find("#gazeta_article_video"),c.length?(d=c.find('iframe[src*="//www.gazeta.tv/plej/19,"]'),e=b.height(),f={width:"30px",height:"30px",padding:"8.5px",position:"absolute",right:"0",top:"-40px",background:"#fff","border-radius":"3px","box-shadow":"0 0 20px 2px rgba(0, 0, 0, 0.3)","box-sizing":"border-box"},h.$closeButton=$('<div class="floatingCloseButton"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13 13"><polygon style="fill:#aaa;fill-rule:evenodd;clip-rule:evenodd;" points="12.9 1.6 11.4 0.2 6.5 5.1 1.5 0.2 0.1 1.6 5.1 6.5 0.1 11.5 1.5 12.9 6.5 7.9 11.4 12.9 12.9 11.5 7.9 6.5 "/></svg></div>'),h.$iframe=d,h.$iframeContainer=d.parent(),h.$mod=c,h.$window=a,h.$xIcon=h.$closeButton.find("polygon"),h.floated=!1,h.iframeHeight=d.height(),h.windowHeight=a.height(),h.$closeButton.css(f),e<=h.windowHeight+h.iframeHeight?h.ready=!1:(!h.floated&&h.isReadyToFloat()&&(h.floated=!0,h.floatPlayer()),h.$window.bind("scroll.floatingPlayer resize.floatingPlayer",function(a){g=!0}),h.intervalId=setInterval(function(){g&&(!h.floated&&h.isReadyToFloat()?(h.floated=!0,h.floatPlayer()):h.floated&&!h.isReadyToFloat()&&(h.floated=!1,h.floatPlayer()),g=!1)},100),h.ready=!0)):h.ready=!1},deinit:function(){var b=a.FloatingPlayer;clearInterval(b.intervalId),b.ready=!1,b.$window.unbind(".floatingPlayer"),b.floated=!1,b.floatPlayer()},floatPlayer:function(){var b,c,d,e,f,g,h,i,j=this,k=48,l=j.$iframe.width(),m=j.$mod[0].getBoundingClientRect(),n=j.$mod.height(),o=l/(j.$iframe.height()-k),p=j.$window.height(),q=j.$window.width();h=j.floated?300:document.getElementById("gazeta_article_video").offsetWidth,i=h/o+k,d={top:p-i-30-m.top-(j.floated?0:100),left:q-h-30-m.left},b={width:h,height:i},c={width:h,height:i,padding:"10px",position:"fixed",right:"20px",bottom:"20px",background:"#fff","border-radius":"3px","box-shadow":"0 0 20px 2px rgba(0, 0, 0, 0.3)","z-index":"2800"},e={width:h,height:i,position:"fixed",right:"30px",bottom:"30px","-webkit-transform":"translate(-"+d.left+"px,-"+d.top+"px)",transform:"translate(-"+d.left+"px,-"+d.top+"px)",transition:"all 0.2s linear","z-index":"2800"},f={width:h,height:i,position:"fixed",top:m.top,"-webkit-transform":"translate("+d.left+"px,"+d.top+"px)",transform:"translate("+d.left+"px,"+d.top+"px)",transition:"all 0.2s linear","z-index":"2800"},g={height:n,"background-color":"#f2f2f2","background-image":"url(//bi.im-g.pl/im/1/20781/m20781691,ZASLEPKA.png)","background-position":"bottom 30px right 30px","background-repeat":"no-repeat"},j.origin||(j.origin=window.location.protocol+"//video.gazeta.pl"),j.floated?(j.$mod.css(g),j.$iframe.css(f),j.$iframe.one("webkitTransitionEnd moztransitionend transitionend oTransitionEnd",function(a){j.$iframe.removeAttr("style"),j.$iframe.css(b),j.$iframeContainer.css(c),j.$iframeContainer.prepend(j.$closeButton)}),a.pushDataToDataLayer({category:"Plywajacy player",action:"Wyswietlenie",label:window.location.href,value:0,nonInteraction:!1,event:"zdarzenie"}),a.sendMessage(j.$iframe[0].contentWindow,j.origin,{action:"floatStatus",data:!0}),a.sendMessage(j.$iframe[0].contentWindow,j.origin,{action:"resize",width:300})):(j.$iframe.css(e),j.$iframeContainer.removeAttr("style"),j.$iframeContainer.find(".floatingCloseButton").detach(),j.$iframe.one("webkitTransitionEnd moztransitionend transitionend oTransitionEnd",function(a){j.$iframe.removeAttr("style"),j.$mod.removeAttr("style")}),a.sendMessage(j.$iframe[0].contentWindow,j.origin,{action:"floatStatus",data:!1}),a.sendMessage(j.$iframe[0].contentWindow,j.origin,{action:"resize",width:document.getElementById("gazeta_article_video").offsetWidth}))},closeFloatingPlayer:function(){var b=a.FloatingPlayer;if(void 0===b.$closeButton)return!1;a.sendMessage(b.$iframe[0].contentWindow,b.origin,{action:"callPause",data:"pause"}),b.deinit(),a.pushDataToDataLayer({category:"Plywajacy player",action:"Zamknij",label:window.location.href,value:0,nonInteraction:!1,event:"zdarzenie"})},closeButtonHandler:function(b,c,d){var e=a.FloatingPlayer;if(void 0===e.$closeButton)return!1;e.origin=b,e.playState=d,c||!c&&"play"!==d?(e.$xIcon.css("fill","#000"),e.$closeButton.css("cursor","pointer"),e.$closeButton.unbind("click",e.closeFloatingPlayer).bind("click",e.closeFloatingPlayer)):(e.$xIcon.css("fill","#aaa"),e.$closeButton.css("cursor","default"),e.$closeButton.unbind("click",e.closeFloatingPlayer))},isFloatingPlayer:function(b){var c=a.FloatingPlayer;return void 0!==c.$iframe&&c.$iframe[0].contentWindow===b},isReadyToFloat:function(){var b=a.FloatingPlayer;return b.$window.scrollTop()>b.$mod.offset().top+b.$mod.height()/4},isEmbededOnArticlePage:function(){return!!window.location.href.match(/\/[17],/)},isDesktopBrowser:function(){return void 0!==gazeta_pl.device?"NOT_MOBILE"==gazeta_pl.device:$("body").hasClass("desk")&&!$("body").hasClass("tablet")},isSafariBrowser:function(){return-1!=navigator.userAgent.indexOf("Safari")&&-1==navigator.userAgent.indexOf("Chrome")&&-1==navigator.userAgent.indexOf("Chromium")}}}}(),gazeta_pl.Player.init();</script>
 +
 +        
 +		<script type="text/javascript">var pfma_wyborcza_version = "1.1.16";</script>
 +		<script type="text/javascript">var szpalty_wyborcza_version = "0.0.2";</script>
 +        <!-- /head script -->
 +	
 +
 +
 +<!-- preloaderModule v1.1 -->
 +
 +<script>
 +	var now = new Date(2019, 0, 31, 17, 10, 30);
 +</script>
 +
 +<!-- currentDateModule v1.0 -->
 +
 +
 +<meta name="Description" content="Filtr ma tak dokadny, e zatrzymuje nawet wirusy i bakterie. Trjka wrocawian stworzya chust, ktra chroni przed smogiem. " />
 +
 +<!-- descriptionsModule v1.0 -->
 +
 +
 +
 +<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
 +
 +
 +	<script>
 +  //<![CDATA[
 +  var gazeta_pl = gazeta_pl || {};
 +
 +  gazeta_pl.documentParam = {"root": "/biznes/"};
 +
 +  gazeta_pl.mobileInfo = {
 +          "isMobileDevice": false
 +          };
 +
 +
 +
 +  gazeta_pl.rootSectionId = 100791;
 +
 +  //]]>
 +  </script>
 +
 +
 +<!-- Bigdata -->
 +<script src="https://bis.gazeta.pl/info/bluewhale/2.5.0/main-min.jsgz"></script> +
 +
 +
 +<!-- portalDataModule v1.0 -->
 + + +<!-- - Agree rd - --> + + + +<!-- v4 rdGid: 1 --> +<!-- v4 Cookie :  --> +<script src="//rodo.agora.pl/agreement/check?gid=1¶ms=&skipMessageOnReject=false"></script> + + + + + + + +	<meta property="og:type" content="article" /> + + + + +<meta property="og:url" content="http://wyborcza.biz/biznes/7,147743,24417936,pomysl-na-biznes-chusta-ktora-chroni-przed-smogiem.html" /> + + + +<!-- Title --> +<meta property="og:title" +	content="Pomys na biznes: chusta, ktra chroni przed smogiem" /> +<meta name="twitter:title" +	content="Pomys na biznes: chusta, ktra chroni przed smogiem" /> + + +	<!-- DESC --> +	<meta property="og:description" +		content="Filtr ma tak dokadny, e zatrzymuje nawet wirusy i bakterie. Trjka wrocawian stworzya chust, ktra chroni przed smogiem. " /> +	<meta name="twitter:description" +		content="Filtr ma tak dokadny, e zatrzymuje nawet wirusy i bakterie. Trjka wrocawian stworzya chust, ktra chroni przed smogiem. " /> + + + +	 +	 +		 +		 +	 +	 +	<!-- IMAGE --> +	<meta property="og:image" content="https://bi.im-g.pl/im/f7/49/17/z24418295FBW,Prace-nad-projektem-chusty-antysmogowej-rozpoczely.jpg" /> +	<meta name="twitter:image" content="https://bi.im-g.pl/im/f7/49/17/z24418295FBW,Prace-nad-projektem-chusty-antysmogowej-rozpoczely.jpg" /> +	<meta name="twitter:card" content="summary_large_image" /> + + + +	<meta property="og:site_name" content="wyborcza.biz" /> + + + +	<meta property="fb:app_id" content="515714931781741" /> + + + + +<meta property="og:locale" content="pl_PL" /> + + + +<!--facebookIdFanpages start --> +<meta property="fb:pages" content="182216700504" /> +<meta property="fb:pages" content="63563473556" /> +<meta property="fb:pages" content="211137058789" /> +<meta property="fb:pages" content="158571784110" /> +<meta property="fb:pages" content="1123290301110277" /> +<meta property="fb:pages" content="110402292339268" /> +<meta property="fb:pages" content="228168274881" /> +<meta property="fb:pages" content="134754663619446" /> +<meta property="fb:pages" content="174480419336" /> +<meta property="fb:pages" content="116460718114" /> +<meta property="fb:pages" content="172897561400" /> +<meta property="fb:pages" content="297584282665" /> +<meta property="fb:pages" content="115721688487368" /> +<meta property="fb:pages" content="178164578911" /> +<meta property="fb:pages" content="336438736394843" /> +<meta property="fb:pages" content="145218379355757" /> +<meta property="fb:pages" content="217794324954867" /> +<meta property="fb:pages" content="145231052239934" /> +<meta property="fb:pages" content="259626760890136"> +<meta property="fb:pages" content="224446291000703" /> +<meta property="fb:pages" content="169554536536974" /> +<meta property="fb:pages" content="189371001102606" /> +<meta property="fb:pages" content="202936473194614" /> +<meta property="fb:pages" content="750649501650020" /> +<meta property="fb:pages" content="732718100095535" /> +<meta property="fb:pages" content="212900872097857" /> +<meta property="fb:pages" content="451218768269373" /> +<meta property="fb:pages" content="164720961114" /> +<meta property="fb:pages" content="1472966049586410" /> +<meta property="fb:pages" content="212665112154600" /> +<meta property="fb:pages" content="186699381425534" /> +<meta property="fb:pages" content="701673749899603" /> +<meta property="fb:pages" content="182114553798" /> +<meta property="fb:pages" content="1509388655954951" /> +<meta property="fb:pages" content="212912818721293" /> +<meta property="fb:pages" content="346589322185" /> +<meta property="fb:pages" content="149902472546" /> +<meta property="fb:pages" content="282927535056444" /> +<meta property="fb:pages" content="196338433729597" /> +<meta property="fb:pages" content="133385026676460" /> +<meta property="fb:pages" content="354565604586" /> +<meta property="fb:pages" content="277864075631246" /> +<meta property="fb:pages" content="175270922504122" /> +<meta property="fb:pages" content="211916968882547" /> +<meta property="fb:pages" content="127659060591519" /> +<meta property="fb:pages" content="217981241618571" /> +<meta property="fb:pages" content="198606720154883" /> +<meta property="fb:pages" content="105171796186333" /> +<meta property="fb:pages" content="1800655190193744" /> +<meta property="fb:pages" content="501324603247131" /> +<meta property="fb:pages" content="159338220786135" /> +<meta property="fb:pages" content="151677518189583" /> +<meta property="fb:pages" content="302385839814255" /> +<meta property="fb:pages" content="200768423330804" /> +<meta property="fb:pages" content="288018984602680" /> +<!--facebookIdFanpages end --> + + +<!-- facebookModule v1.1 --> +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +    <!-- (C)2000-2014 Gemius SA - gemiusAudience -->
 +    <script type="text/javascript">
 +        <!--//--><![CDATA[//><!--        	
 +        var pp_gemius_identifier = new String('citFBk7kdQKEPMwqCmPRXqSc.IlGzFiuWwxH9NXy4Zb.a7/arg=147743');
 +        
 +        function gemius_pending(i) { window[i] = window[i] || function() {var x = window[i+'_pdata'] = window[i+'_pdata'] || []; x[x.length]=arguments;};};
 +        setTimeout(function() {
 +            gemius_pending('gemius_hit'); gemius_pending('gemius_event'); gemius_pending('pp_gemius_hit'); gemius_pending('pp_gemius_event');
 +            (function(d,t) {try {var gt=d.createElement(t),s=d.getElementsByTagName(t)[0],l=((location.protocol=='https:')?'https://bis.gazeta.pl/info/http/xgemius-min.jsgz':'http://static.gazeta.pl/info/http/xgemius-min.jsgz'); gt.setAttribute('async','async');
 +                gt.setAttribute('defer','defer'); gt.src=l; s.parentNode.insertBefore(gt,s);} catch (e) {}})(document,'script');
 +        }, 50);
 +        //--><!]]>
 +    </script>
 +    
 +
 +
 +<!-- gemiusModule v1.1 -->
 +
 +
 +
 +<meta name="Keywords" content="Konsument i zakupy">
 +
 +	<meta name="news_keywords" content="Konsument i zakupy">
 +
 +
 +<!-- keywordsModule v1.0 -->
 +
 +
 +
 +
 +<meta name="google-site-verification" content="3P4BE3hLw82QWqtseIE60qQcOtrpMxMnCNkcv62pjTA" />
 +
 +
 +	
 +        <!-- Google Tag Manager --> <noscript><iframe src="//www.googletagmanager.com/ns.html?id=GTM-W4DZP5" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript> <script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= '//www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); })(window,document,'script','dataLayer','GTM-W4DZP5');</script> <!-- End Google Tag Manager -->
 +    
 +	
 +
 +
 +<!-- googleModule v1.0 -->
 +
 +
 +
 +
 +	
 +	
 +		<meta name="viewport" content="width=device-width, user-scalable=yes">
 +	
 +
 +
 +<!-- viewportModule v1.0 -->
 +
 +
 +
 +
 +
 +	
 +    
 +        <!-- wyborcza_common_style not set -->
 +        
 +            
 +                <link rel="stylesheet" href="//static.im-g.pl/css/wyborcza-common/builds/18.0.6/style-min.cssgz" />
 +            
 +            
 +        
 +    
 +
 +
 +<!-- wyborcza_common_style /css/wyborcza-biz/version-mobile.htm -->
 +<!-- wyborcza_common_style /css/wyborcza-biz/version-desktop.htm -->
 +
 +	
 +		<link rel="stylesheet" href="//static.im-g.pl/wyborcza/wyborcza-biz/css/1.0.9/style-min.cssgz" />
 +	
 +	
 +
 +
 +<!-- stylesModule v1.0 -->
 +
 +
 +
 +
 +	<link rel="alternate" type="application/rss+xml" title="Wiadomoci gospodarcze" href="http://wyborcza.biz/pub/rss/wyborcza_biz_wiadomosci.htm">
 +
 +
 +<!-- rssModule v1.0 -->
 +
 +
 +<!-- Facebook Pixel Code -->
 +<script>
 +	!function(f, b, e, v, n, t, s) {
 +		if (f.fbq)
 +			return;
 +		n = f.fbq = function() {
 +			n.callMethod ? n.callMethod.apply(n, arguments) : n.queue.push(arguments)
 +		};
 +		if (!f._fbq)
 +			f._fbq = n;
 +		n.push = n;
 +		n.loaded = !0;
 +		n.version = '2.0';
 +		n.queue = [];
 +		t = b.createElement(e);
 +		t.async = !0;
 +		t.src = v;
 +		s = b.getElementsByTagName(e)[0];
 +		s.parentNode.insertBefore(t, s)
 +	} (window, document, 'script', 'https://connect.facebook.net/en_US/fbevents.js');
 +	fbq('init', '534530363346991'); // Insert your pixel ID here.
 +	fbq('track', 'PageView');
 +</script>
 +
 +<noscript>
 +	<img height="1" width="1" style="display: none"	src="https://www.facebook.com/tr?id=534530363346991&ev=PageView&noscript=1" />
 +</noscript>
 +
 +<!-- DO NOT MODIFY -->
 +<!-- End Facebook Pixel Code -->
 +
 +<!-- facebookPixelModule v1.0 -->
 +
 +
 +<script>
 +	var wyborcza_pl = wyborcza_pl || {};
 +	wyborcza_pl.LokaleGeolocationCityId = '';
 +</script>
 +
 +<!-- geolocationModule v1.0 -->
 +
 +
 +
 +
 +
 +<!-- browserNotificationEnableModule -->
 +
 +
 +
 +
 +
 +
 +
 +	<script type="text/javascript">
 +		var _sf_async_config = _sf_async_config || {};
 +		_sf_async_config.uid = 14371;
 +		_sf_async_config.domain = 'wyborcza.biz';
 +		_sf_async_config.flickerControl = false;
 +
 +		
 +			
 +			   _sf_async_config.useCanonical = true;
 +			
 +			
 +		
 +
 +		var _sf_startpt = (new Date()).getTime();
 +	</script>
 +
 +	<script async src="//static.chartbeat.com/js/chartbeat_mab.js"></script>
 +
 +
 +<!-- chartbeatHeadModule v1.0 -->
 + +<!--10184895, [ null ], aggregatorModule--> +
 +	
 +
 +
 +
 +  + + + + + + +<script type='text/javascript'> + +	 + + +	 + +		 + +		 + +		 + +		 + +		 + +		 + +		 + +		 + +		 + +		 +			if(!window.AG){ +    window.AG = window.AG || {}; +    window.AG.rodoAccepted = -1; +} +		 + +		 + +		 + +		 + +		 + +		 + +		 + +		 + +		 + +		 + +		 + + +     +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +				var +    adviewDFP = adviewDFP || {}; +adviewDFP.DFPTargeting = []; +			 +	 +			 +	 +			 +				// [HB] Core-prod-1 - HEADER_START +var PREBID_TIMEOUT = (window.AG && window.AG.rodoAccepted === 1) ? 500 : 0; +var pbjs_currency = 1; // -> to $1 +var pbjs_ga = true; +var pbjs_yb_hb = false; +var eur2usd = 1.1; +var yb_dosamplerate = 100; +var yb_seg = ''; +var _st0=new Date(); + +// BIG data +BigData = { +    notify: function(eventName){ +        var event = BigData.createEvent(eventName); +        BigData.fireEvent(event); +    }, +    createEvent: function(eventName) { +        var event; +        if (document.createEvent) { +            event = document.createEvent("HTMLEvents"); +            event.initEvent(eventName, true, true); +        } else { +            event = document.createEventObject(); +            event.eventType = eventName; +        } +        event.eventName = eventName; +        return event; +    }, +    fireEvent: function(event) { +        if (document.createEvent) { +            document.dispatchEvent(event); +        } else { +            document.fireEvent("on" + event.eventType, event); +        } +    } +}; +//bwGuidv2 +function getCookie(cname) { +    var name = cname + "="; +    var ca = document.cookie.split(';'); +    for(var i=0; i<ca.length; i++) { +        var c = ca[i]; +        while (c.charAt(0)==' ') c = c.substring(1); +        if (c.indexOf(name) == 0) return c.substring(name.length, c.length); +    } +    return ""; +} +function getJSON (url, callback) { +	var xhr = new XMLHttpRequest(); +	xhr.open("get", url, true); +	xhr.responseType = "json"; +	xhr.onload = function() { +		var status = xhr.status; +		if (status == 200) { +			callback(null, xhr.response); +		} else { +			callback(status); +		} +	}; +	xhr.send(); +}; +//***************************************** + +if(Math.random()*100 <= yb_dosamplerate ) { +	var bwGuidv2 = getCookie("bwGuidv2"); +	if (bwGuidv2 != "") { +		getJSON("https://squid.gazeta.pl/do/p/v1/gpfgo?g=" + bwGuidv2 + "&p=30000", +			function(err, data) { +			  if (err != null) { +			    console.log("getJSON: Something went wrong: " + err); +			  } else { +			  	try{ +			  		var segments = []; +				    for(index in data.profile.features){ +				    	segments.push(String(index)); +				    } +				    yb_seg = segments; +			  	}catch(e){} +			  } +		});	 +	} +} +document.write('\x3Cscript type="text/javascript" src="https://bi.adview.pl/static/adview/front/master/dfp/tools/adview/prebid.js">\x3C/script>'); + +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 + +	 +	 +			 +	 +			 +	 +			 +	 +			 +				// 20181218 + +var adUnits = [{ +  //wyborcza.biz_001-TOPBOARD +  code: 'div-gpt-ad-001-TOPBOARD-0', +  sizes: [[970,250]],  +  bids: [{ +        bidder: 'appnexus', +        params: { placementId: '11549538' } +       },{ +        bidder: 'adform', +        params: { mid: '427595' } +       },{ +        bidder: 'pubmatic', +        params: { publisherId: '155949', adSlot: 'wyborcza.biz_001-TOPBOARD@970x250' } +       },{ +        bidder: 'ix',  +        params: { size: [970,250], siteId: '227220' } +       },{ +        bidder: 'rtbhouse', +        params: { region: 'prebid-eu', publisherId: 'da39a3ee5e6b4b0d' } +       },{ +        bidder: 'connectad', +        params: { networkId: '10047', siteId: '1013785' } +       }] +},{ +  //wyborcza.biz_003-RECTANGLE +  code: 'div-gpt-ad-003-RECTANGLE-0', +  sizes: [[300,600]],  +  bids: [{ +        bidder: 'appnexus', +        params: { placementId: '11549537' } +       },{ +        bidder: 'adform', +        params: { mid: '427594' } +       },{ +        bidder: 'pubmatic', +        params: { publisherId: '155949', adSlot: 'wyborcza.biz_003-RECTANGLE@300x600' } +       },{ +        bidder: 'ix',  +        params: { size: [300,600], siteId: '227219' } +       },{ +        bidder: 'rtbhouse', +        params: { region: 'prebid-eu', publisherId: 'da39a3ee5e6b4b0d' } +       },{ +        bidder: 'connectad', +        params: { networkId: '10047', siteId: '1013784' } +       }] +}]; +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 + +	 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +				// [HB] Core-prod-2 - OVER_GT +if(pbjs_yb_hb){ +	    var yb_hb = (pbjs_yb_hb ? Math.round(Math.random()) : 1); +	    PREBID_TIMEOUT *= yb_hb; +	} +    var pbjs = pbjs || {}; +    pbjs.que = pbjs.que || []; +    pbjs.que.push(function() { +    	for (var i = 0; i < adUnits.length; i++){shuffle(adUnits[i].bids);} +  		if(PREBID_TIMEOUT>0) pbjs.addAdUnits(adUnits); + +		  /* pass consent strings - START */ +		   +		  getCookie = function (name) { +			  var value = "; " + document.cookie; +			  var parts = value.split("; " + name + "="); +			  if (parts.length == 2) return parts.pop().split(";").shift(); +		  } + +		  pbjs.setConfig({ +			  consentManagement: { +				  cmpApi: 'static', +				  allowAuctionWithoutConsent: true, +				  consentData: { +					  getConsentData: { +						  'gdprApplies': false, +						  'hasGlobalScope': false, +						  'consentData': getCookie('euconsent') +					  }, +					  getVendorConsents: { +						  'metadata': getCookie('euconsent') +					  } +				  } +			  } +		  }); +		  /* pass consent string - END */ + +        pbjs.requestBids({ +            bidsBackHandler: function(bidResponses) { +                pbjs.dataPrebidReady=true; +                BigData.notify("data-prebid-ready"); +            } +        }); +		pbjs.bidderSettings = { +			standard: { +			  adserverTargeting: [{ +			    key: "hb_bidder", val: function(bidResponse) { return bidResponse.bidderCode; }},{ +			    key: "hb_adid", val: function(bidResponse) { return bidResponse.adId; }},{ +			    key: "hb_pb", val: function(bidResponse) { return (bidResponse.pbHg*pbjs_currency).toFixed(2); }},{ +			    key: "hb_size", val: function(bidResponse) { return bidResponse.size; } +			  }] +			}, +		    audienceNetwork: { bidCpmAdjustment : function(bidCpm){ return (bidCpm * 0.75); }}, +			districtmDMX: { bidCpmAdjustment : function(bidCpm){ return (bidCpm * 0.85); }}, +			aol: { bidCpmAdjustment : function(bidCpm){ return (bidCpm * 0.8); }}, +			adform: { bidCpmAdjustment : function(bidCpm){ return (bidCpm * 0.9)*eur2usd; }}, +			rubicon: { bidCpmAdjustment : function(bidCpm){ return (bidCpm * 0.82)*eur2usd; }}, +			smartadserver: { bidCpmAdjustment : function(bidCpm){ return (bidCpm * 0.9); }}, +			pubmatic: { bidCpmAdjustment : function(bidCpm){ return (bidCpm * 0.9); }},  +		  	appnexusAst: { bidCpmAdjustment : function(bidCpm){ return (bidCpm * 0.65); }} +		}; +	  if(pbjs_ga) { +		(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ +		  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), +		  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) +		  })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); +		ga('create', 'UA-84607356-7', 'auto', 'yb', {'sampleRate': 0.1}); +		pbjs.enableAnalytics({ +		  provider: 'ga', +		  options: { +		    global: 'ga', // String name of GA global. Default is 'ga' +		    enableDistribution: false, +		    trackerName: 'yb' +		  } +		});     +	  } +    }); +	function shuffle(a) { +	  var i = 0, j = 0, temp = null; +	  for (i = a.length-1; i > 0; i--) { +	    j = Math.floor(Math.random() * (i + 1)); +	    temp = a[i]; +	    a[i] = a[j]; +	    a[j] = temp; +	  } +	} + +var lazyGPT = true; +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 + +	 + +	var googletag = googletag || {}; +	googletag.cmd = googletag.cmd || []; +	var lazyGPT = lazyGPT || false; + +	if(!lazyGPT) { + +	(function() { +        var useSSL = 'https:' == document.location.protocol; +        var src = (useSSL ? 'https:' : 'http:') + +            '//www.googletagservices.com/tag/js/gpt.js'; +        document.write('<scr' + 'ipt src="' + src + '"></scr' + 'ipt>'); +      })(); +    } + +	var dfpParams = dfpParams || {}; +	dfpParams.video = dfpParams.video || {}; +	dfpParams.slots = dfpParams.slots || {}; +	dfpParams.jsp = 23; +	dfpParams.dir = 'biznes'; +	dfpParams.dx = '147743'; +	 +	dfpParams.prefix = '/75224259/AGORA-IN/Wyborcza.biz'; +	    +	      +			dfpParams.video.preroll = '//pubads.g.doubleclick.net/gampad/ads?sz=400x300|640x480&iu=/75224259/AGORA-IN/Wyborcza.biz/090-PREROLL&cust_params=pos%3D090-PREROLL%26dx%3D147743%26jsp%3D23%26dir%3Dbiznes%26kw%3D[brandsafe]%26dystrybutor%3D[distributor_id]%26passback_id%3D[passback_id]%26domena%3D[adview_hostname]%26cb%3D[cb]&url=[locationhref]&description_url=[locationhref]&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&correlator=[timestamp]'; +		  +			dfpParams.video.skin = '//pubads.g.doubleclick.net/gampad/ads?sz=400x300|640x480&iu=/75224259/AGORA-IN/Wyborcza.biz/120-SKIN&cust_params=pos%3D120-SKIN%26dx%3D147743%26jsp%3D23%26dir%3Dbiznes%26kw%3D[brandsafe]%26dystrybutor%3D[distributor_id]%26passback_id%3D[passback_id]%26domena%3D[adview_hostname]%26cb%3D[cb]&url=[locationhref]&description_url=[locationhref]&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&correlator=[timestamp]'; +		  +			dfpParams.video.overlay = '//pubads.g.doubleclick.net/gampad/ads?sz=400x300|640x480&iu=/75224259/AGORA-IN/Wyborcza.biz/123-OVERLAY&cust_params=pos%3D123-OVERLAY%26dx%3D147743%26jsp%3D23%26dir%3Dbiznes%26kw%3D[brandsafe]%26dystrybutor%3D[distributor_id]%26passback_id%3D[passback_id]%26domena%3D[adview_hostname]%26cb%3D[cb]&url=[locationhref]&description_url=[locationhref]&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&correlator=[timestamp]'; +		  +			dfpParams.video.pausead = '//pubads.g.doubleclick.net/gampad/ads?sz=400x300|640x480&iu=/75224259/AGORA-IN/Wyborcza.biz/126-PAUSEAD&cust_params=pos%3D126-PAUSEAD%26dx%3D147743%26jsp%3D23%26dir%3Dbiznes%26kw%3D[brandsafe]%26dystrybutor%3D[distributor_id]%26passback_id%3D[passback_id]%26domena%3D[adview_hostname]%26cb%3D[cb]&url=[locationhref]&description_url=[locationhref]&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&correlator=[timestamp]'; +		  +	   +	   +	      +			dfpParams.slots['021-IMK'] = { url: "fif.htm?adUnit=/75224259/AGORA-IN/Wyborcza.biz/021-IMK&adUnitCode=021-IMK&adUnitSize=[[300,90],'fluid']&dx=147743&dir=biznes&jsp=23", sizes: [[300,90],'fluid'], autoLoad: false, autoLoadMargin: 0 }; +		  +			dfpParams.slots['003-RECTANGLE'] = { url: "fif.htm?adUnit=/75224259/AGORA-IN/Wyborcza.biz/003-RECTANGLE&adUnitCode=003-RECTANGLE&adUnitSize=[[300,250],[300,600],[160,600],[120,600]]&dx=147743&dir=biznes&jsp=23", sizes: [[300,250],[300,600],[160,600],[120,600]], autoLoad: false, autoLoadMargin: 200 }; +		  +			dfpParams.slots['007-CONTENTBOARD'] = { url: "fif.htm?adUnit=/75224259/AGORA-IN/Wyborcza.biz/007-CONTENTBOARD&adUnitCode=007-CONTENTBOARD&adUnitSize=[[300,250],[320,250],[336,280],[620,200],'fluid']&dx=147743&dir=biznes&jsp=23", sizes: [[300,250],[320,250],[336,280],[620,200],'fluid'], autoLoad: false, autoLoadMargin: 200 }; +		  +			dfpParams.slots['071-WINIETA'] = { url: "fif.htm?adUnit=/75224259/AGORA-IN/Wyborcza.biz/071-WINIETA&adUnitCode=071-WINIETA&adUnitSize=[[940,140],[940,70]]&dx=147743&dir=biznes&jsp=23", sizes: [[940,140],[940,70]], autoLoad: false, autoLoadMargin: 200 }; +		  +			dfpParams.slots['035-RECTANGLE-BTF'] = { url: "fif.htm?adUnit=/75224259/AGORA-IN/Wyborcza.biz/035-RECTANGLE-BTF&adUnitCode=035-RECTANGLE-BTF&adUnitSize=[[300,250]]&dx=147743&dir=biznes&jsp=23", sizes: [[300,250]], autoLoad: false, autoLoadMargin: 0 }; +		  +			dfpParams.slots['067-RECTANGLE-BTF'] = { url: "fif.htm?adUnit=/75224259/AGORA-IN/Wyborcza.biz/067-RECTANGLE-BTF&adUnitCode=067-RECTANGLE-BTF&adUnitSize=[[300,250]]&dx=147743&dir=biznes&jsp=23", sizes: [[300,250]], autoLoad: false, autoLoadMargin: 0 }; +		  +			dfpParams.slots['091-RELATED'] = { url: "fif.htm?adUnit=/75224259/AGORA-IN/Wyborcza.biz/091-RELATED&adUnitCode=091-RELATED&adUnitSize=[[155,290],'fluid']&dx=147743&dir=biznes&jsp=23", sizes: [[155,290],'fluid'], autoLoad: false, autoLoadMargin: 200 }; +		  +			dfpParams.slots['019-TOPLAYER'] = { url: "fif.htm?adUnit=/75224259/AGORA-IN/Wyborcza.biz/019-TOPLAYER&adUnitCode=019-TOPLAYER&adUnitSize=[]&dx=147743&dir=biznes&jsp=23", sizes: [], autoLoad: false, autoLoadMargin: 200 }; +		  +			dfpParams.slots['001-TOPBOARD'] = { url: "fif.htm?adUnit=/75224259/AGORA-IN/Wyborcza.biz/001-TOPBOARD&adUnitCode=001-TOPBOARD&adUnitSize=[[728,90],[750,100],[750,200],[750,300],[940,300],[970,250],[1170,300]]&dx=147743&dir=biznes&jsp=23", sizes: [[728,90],[750,100],[750,200],[750,300],[940,300],[970,250],[1170,300]], autoLoad: false, autoLoadMargin: 200 }; +		  +			dfpParams.slots['042-FOOTBOARD'] = { url: "fif.htm?adUnit=/75224259/AGORA-IN/Wyborcza.biz/042-FOOTBOARD&adUnitCode=042-FOOTBOARD&adUnitSize=[[728,90],[750,100],[750,200],[750,300],[940,300],[970,250],[940,1200]]&dx=147743&dir=biznes&jsp=23", sizes: [[728,90],[750,100],[750,200],[750,300],[940,300],[970,250],[940,1200]], autoLoad: true, autoLoadMargin: 20 }; +		  +	   +	 + +</script> + +<!-- IBA Analytics --> +<!-- 20180116 --> +<script type="text/javascript"> +    var IBA=IBA||{};IBA.dfp={adUnits:[],rendered:[],exmplUrl:null,onSlotRendered:function(t){null===IBA.dfp.exmplUrl&&(IBA.dfp.exmplUrl=t.slot.ba);var e=t.slot.getAdUnitPath();IBA.dfp.adUnits.push(e),t.isEmpty===!1&&IBA.dfp.rendered.push(e),IBA.adb.update(),"4"==IBA.adb.status&&IBA.adb.onReady()},update:function(){var t=[],e=[];if("undefined"!=typeof googletag&&"function"==typeof googletag.pubads&&"undefined"!=typeof googletag.pubads().getSlots)for(var a=googletag.pubads().getSlots(),n=0;n<a.length;n++)null!==a[n].J&&e.push(a[n].getAdUnitPath()),t.push(a[n].getAdUnitPath());e.length>IBA.dfp.rendered.length&&(IBA.dfp.rendered=e),t.length>IBA.dfp.adUnits.length&&(IBA.dfp.adUnits=t),IBA.adb.onReady()},testUrlAndDispatch:function(){var t=!1,e=new XMLHttpRequest;if(e.open("HEAD",IBA.dfp.exmplUrl,!0),e.onreadystatechange=function(){console.log(e.readyState+" | status|  "+e.status),0===e.status&&(IBA.adb.status="5"),IBA.adb.dispatch()},IBA.dfp.exmplUrl)try{e.send(),t=!0}catch(a){IBA.adb.status="5",IBA.adb.dispatch()}else console.warn("IBA exmplUrl"+IBA.dfp.exmplUrl)}},IBA.adb={states:{0:"Not available",1:"Partially available but not rendered",2:"Partially available at least one rendered",3:"Available but not rendered",4:"Available at least one rendered",5:"Not available but lib loaded",9:"Default : unknown"},gem:{0:"AfHgvarrP4PC7igzOLWh7dVpTGP109BuRIffksuYjfT.c7",1:"ofhA3bsKT7xY8Cgj4cuRX8cQ.qiY_fsP1VURxgCyYv3.F7",2:"ofhA3bsKT7xY8Cgj4cuRX8cQ.qiY_fsP1VURxgCyYv3.F7",3:"nF5K4QQDK9TNGw_ql.fdc9U7XrD1irt.GoVhWXaFu6b.Z7",4:"nF5K4QQDK9TNGw_ql.fdc9U7XrD1irt.GoVhWXaFu6b.Z7",5:"ofhA3bsKT7xY8Cgj4cuRX8cQ.qiY_fsP1VURxgCyYv3.F7",9:"__unknown__"},status:"9",update:function(){var t=IBA.dfp.adUnits.length,e=IBA.dfp.rendered.length,a=window.abp||!1;0===t?IBA.adb.status="0":0===e&&a===!0?IBA.adb.status="1":e>0&&a===!0?IBA.adb.status="2":0===e?IBA.adb.status="3":e>0&&(IBA.adb.status="4")},onReady:function(){IBA.adb.ready!==!0&&(IBA.adb.ready=!0,IBA.adb.update(),"3"==IBA.adb.status?IBA.dfp.testUrlAndDispatch():IBA.adb.dispatch())},ready:!1,dispatch:function(){var t=IBA.adb.status;window.gemius_identifier=IBA.adb.gem[t];var e=document.createElement("script"),a=document.getElementsByTagName("script")[0];e.onerror=function(t){},e.src="//gazeta.hit.gemius.pl/gemius.js",a.parentNode.insertBefore(e,a),IBA.BigData.notify("adblockdataready")}},IBA.BigData={notify:function(t){var e=IBA.BigData.createEvent(t);IBA.BigData.fireEvent(e)},createEvent:function(t){var e;return document.createEvent?(e=document.createEvent("HTMLEvents"),e.initEvent(t,!0,!0)):(e=document.createEventObject(),e.eventType=t),e.eventName=t,e},fireEvent:function(t){document.createEvent?document.dispatchEvent(t):document.fireEvent("on"+t.eventType,t)}},setTimeout(function(){try{"complete"===document.readyState?IBA.dfp.update():window.addEventListener("load",IBA.dfp.update,!1)}catch(t){}},20); +</script> + + + + +	 + +	 +		 +		<!-- eyeoH -->  +<!-- eyeoHeadScripts 1.0 -->  +<script type='text/javascript'>var abp;</script>  +<script type='text/javascript' src='/aliasy/adp/px.js?ch=1'></script>  +<script type='text/javascript' src='/aliasy/adp/px.js?ch=2'></script> +<!-- /eyeoH --> +		 +	 +		 +	 +		 +	 +		 +	 +		 +	 +		 +	 +		 +	 +		 +	 +		 +	 +		 +	 +		 +	 +		 +	 +		 +	 +		 +	 +		 +	 +		 +	 +		 +	 +		 +	 +		 +	 +		 +	 + +	<script type='text/javascript'> +	googletag.cmd.push(function() { + +		var mobileSizeMap = googletag.sizeMapping() +				.addSize([336,0], [[1,1],[300,250],[320,250],[300,150],[320,150],[336,280],[267,310],[155,290],[320,100],[320,50],[320,125],[320,266],[320,300],[200,130],[300,600], 'fluid']) +				.addSize([0,0],   [[1,1],[300,250],[320,250],[300,150],[320,150],[267,310],[155,290],[320,100],[320,50],[320,125],[320,266],[320,300],[200,130],[300,600], 'fluid']) +				.build(); + +		 +				 +					 +						var mobileSizeMap_101_topboard_mobi = googletag.sizeMapping().addSize([336,0], [[300,250],[320,250],[300,150],[320,150],[336,280]]).addSize([0,0], [[300,250],[320,250],[300,150],[320,150]]).build(); +					 +						var mobileSizeMap_104_rectangle_mobi = googletag.sizeMapping().addSize([336,0], [[300,250],[320,250],[300,150],[320,150],[336,280],[300,600],'fluid']).addSize([0,0], [[300,250],[320,250],[300,150],[320,150],[300,600],'fluid']).build(); +					 +						var mobileSizeMap_108_footboard_mobi = googletag.sizeMapping().addSize([336,0], [[300,250],[320,250],[300,150],[320,150],[336,280]]).addSize([0,0], [[300,250],[320,250],[300,150],[320,150]]).build(); +					 +						var mobileSizeMap_091_related = googletag.sizeMapping().addSize([336,0], [[155,290],'fluid']).addSize([0,0], [[155,290],'fluid']).build(); +					 +						var mobileSizeMap_150_adboard_a_mobi = googletag.sizeMapping().addSize([336,0], [[300,250],[320,250],[300,150],[320,150],[336,280]]).addSize([0,0], [[300,250],[320,250],[300,150],[320,150]]).build(); +					 +						var mobileSizeMap_019_toplayer_mobi = googletag.sizeMapping().addSize([336,0], []).addSize([0,0], []).build(); +					 +				 +		 + +		googletag.defineSlot('/75224259/AGORA-IN/Wyborcza.biz/001-TOPBOARD', [[728,90],[750,100],[750,200],[750,300],[940,300],[970,250],[1170,300]], 'div-gpt-ad-001-TOPBOARD-0').setTargeting('pos', ['001-TOPBOARD']).addService(googletag.pubads()); +googletag.defineSlot('/75224259/AGORA-IN/Wyborcza.biz/003-RECTANGLE', [[300,250],[300,600],[160,600],[120,600]], 'div-gpt-ad-003-RECTANGLE-0').setTargeting('pos', ['003-RECTANGLE']).addService(googletag.pubads()); +googletag.defineSlot('/75224259/AGORA-IN/Wyborcza.biz/067-RECTANGLE-BTF', [[300,250]], 'div-gpt-ad-067-RECTANGLE-BTF-0').setTargeting('pos', ['067-RECTANGLE-BTF']).addService(googletag.pubads()); +googletag.defineSlot('/75224259/AGORA-IN/Wyborcza.biz/071-WINIETA', [[940,140],[940,70]], 'div-gpt-ad-071-WINIETA-0').setTargeting('pos', ['071-WINIETA']).addService(googletag.pubads()); +googletag.defineSlot('/75224259/AGORA-IN/Wyborcza.biz/007-CONTENTBOARD', [[300,250],[320,250],[336,280],[620,200],'fluid'], 'div-gpt-ad-007-CONTENTBOARD-0').setTargeting('pos', ['007-CONTENTBOARD']).addService(googletag.pubads()); +googletag.defineSlot('/75224259/AGORA-IN/Wyborcza.biz/035-RECTANGLE-BTF', [[300,250]], 'div-gpt-ad-035-RECTANGLE-BTF-0').setTargeting('pos', ['035-RECTANGLE-BTF']).addService(googletag.pubads()); + +		 +			googletag.pubads().setTargeting('dx','147743'); +		 +			googletag.pubads().setTargeting('dir','biznes'); +		 +			googletag.pubads().setTargeting('jsp','23'); +		 +      googletag.pubads().setTargeting('cb', [''+window.AG.rodoAccepted]); +			googletag.pubads().setTargeting('uponit','false'); + + +		 + +		 +			 +		 +			 +				var _YB=_YB||{ab:function(){return (Math.random()>=0.1?"b":"a"+Math.floor(Math.random()*10));}}; +    googletag.pubads().getSlots().forEach(function(slot){slot.setTargeting('yb_ab', _YB.ab());}); +			 +		 +			 +				var +    cookieIfr = null; +if (document.referrer) { + +    var +        url_domain = function(data) { +                var a = document.createElement('a'); +                a.href = data; +                return a.protocol + '//' + a.hostname; +            }; + +    var xhttp = new XMLHttpRequest(); +    xhttp.onreadystatechange = function() { +        if (this.readyState == 4 && this.status == 200) { +            cookieIfr = JSON.parse(this.responseText).dfpreferrer; +        } +    }; +    xhttp.withCredentials = true; +    xhttp.open('GET', '//dfp.gazeta.pl/c.servlet?cn=dfpreferrer', false); +    try { +        xhttp.send(); +    } catch (ex) { +        console.info('Error xhttp DFP'); +    } +} + +function _setCookieDFP(name) { +    var xhttp = new XMLHttpRequest(); +    xhttp.withCredentials = true; +    xhttp.open('GET', '//dfp.gazeta.pl/c.servlet?cn=dfpreferrer&cv=' + name, true); +    try { +        xhttp.send(); +    } catch (ex) { +        console.info('Error xhttp DFP'); +    } +} + +function _getCookieValueDFP(a) { +    var b = document.cookie.match('(^|;)\\s*' + a + '\\s*=\\s*([^;]+)'); +    return b ? b.pop() : ''; +} + + + + +// 20180410 +var hashArrDFP = ['BoxBizCzol1','BoxBizImg1','BoxBizImg2','BoxBizImg3','BoxBizLink','BoxBizLinkImg','BoxBizMT','BoxBizNav','BoxGWImg','BoxHor','BoxMTPromoImg','BoxMTPromo','BoxKultIko','BoxKultImg1','BoxKultImg2','BoxKultImg3','BoxKultLink','BoxKultLinkImg','BoxKultNav','BoxKultNavLink','BoxLoBbImg1','BoxLoBbImg2','BoxLoBbImg3','BoxLoBbImg4','BoxLoBbLink','BoxLoBbMT','BoxLoBbNavLink','BoxLoBiImg1','BoxLoBiImg2','BoxLoBiImg3','BoxLoBiImg4','BoxLoBiLink','BoxLoBiMT','BoxLoByImg1','BoxLoByImg2','BoxLoByImg3','BoxLoByImg4','BoxLoByLink','BoxLoByMT','BoxLoByNavLink','BoxLoCzImg1','BoxLoCzImg2','BoxLoCzImg3','BoxLoCzImg4','BoxLoCzNav','BoxLoCzLink','BoxLoCzMT','BoxLoCzNavLink','BoxLoGLImg1','BoxLoGLImg2','BoxLoGLImg3','BoxLoGLImg4','BoxLoGLLink','BoxLoGLMT','BoxLoGLNavLink','BoxLoGwImg1','BoxLoGwImg2','BoxLoGwImg3','BoxLoGwImg4','BoxLoGwLink','BoxLoGwMT','BoxLoKaImg1','BoxLoKaImg2','BoxLoKaImg3','BoxLoKaImg4','BoxLoKaLink','BoxLoKaMT','BoxLoKaNavLink','BoxLoKiImg1','BoxLoKiImg2','BoxLoKiImg3','BoxLoKiImg4','BoxLoKiLink','BoxLoKiMT','BoxLoKiNavLink','BoxLoKrImg1','BoxLoKrImg2','BoxLoKrImg3','BoxLoKrImg4','BoxLoKrLink','BoxLoKrMT','BoxLoKrNavLink','BoxLoLoImg1','BoxLoLoImg2','BoxLoLoImg3','BoxLoLoImg4','BoxLoLoLink','BoxLoLoMT','BoxLoLoNavLink','BoxLoLuImg1','BoxLoLuImg2','BoxLoLuImg3','BoxLoLuImg4','BoxLoLuLink','BoxLoLuMT','BoxLoLuNavLink','BoxLoOlsztynImg1','BoxLoOlsztynImg2','BoxLoOlsztynImg3','BoxLoOlsztynImg4','BoxLoOlsztynLink','BoxLoOlsztynMT','BoxLoOlsztynNavLink','BoxLoOlsztynNav','BoxLoOpImg1','BoxLoOpImg2','BoxLoOpImg3','BoxLoOpImg4','BoxLoOpLink','BoxLoOpMT','BoxLoOpNavLink','BoxLoPlImg1','BoxLoPlImg2','BoxLoPlImg3','BoxLoPlImg4','BoxLoPlLink','BoxLoPlMT','BoxLoPoImg1','BoxLoPoImg2','BoxLoPoImg3','BoxLoPoImg4','BoxLoPoLink','BoxLoPoMT','BoxLoPoNavLink','BoxLoRaImg1','BoxLoRaImg2','BoxLoRaImg3','BoxLoRaImg4','BoxLoRaLink','BoxLoRaMT','BoxLoRaNavLink','BoxLoRzImg2','BoxLoRzImg3','BoxLoRzImg4','BoxLoRzLink','BoxLoRzNavLink','BoxLoSoImg1','BoxLoSoImg2','BoxLoSoImg3','BoxLoSoImg4','BoxLoSoLink','BoxLoSoMT','BoxLoSoNavLink','BoxLoSzImg1','BoxLoSzImg2','BoxLoSzImg3','BoxLoSzImg4','BoxLoSzLink','BoxLoSzMT','BoxLoSzNavLink','BoxLoTrImg1','BoxLoTrImg2','BoxLoTrImg3','BoxLoTrImg4','BoxLoTrLink','BoxLoTrMT','BoxLoTrNavLink','BoxLoTrNav','BoxLoWaImg1','BoxLoWaImg2','BoxLoWaImg3','BoxLoWaImg4','BoxLoWaLink','BoxLoWaMT','BoxLoWaNav','BoxLoWrImg1','BoxLoWrImg2','BoxLoWrImg3','BoxLoWrImg4','BoxLoWrLink','BoxLoWrMT','BoxLoWrNavLink','BoxLoZgImg1','BoxLoZgImg2','BoxLoZgImg3','BoxLoZgImg4','BoxLoZgLink','BoxLoZgMT','BoxLoZgNavLink','BoxLSCzol1','BoxLSCzol2','BoxMotMT','BoxMotImg1','BoxMotImg2','BoxMotImg3','BoxMotImg4','BoxMotLink','BoxMotLinkImg','BoxMotNav','BoxLSFCZOL1','BoxLSFImg1','BoxLSFImg2','BoxLSFImg3','BoxLSFImg4','BoxLSFLink','BoxLSFMT','BoxLSImg1','BoxLSImg2','BoxLSLink','BoxLSLinkImg','BoxLSMT','BoxLSNav','ECON','BoxNewestNav','BoxNewsImg1','BoxNewsImg2','BoxNewsImg3','BoxNewsImg4','BoxNewsLink','BoxNewsLinkImg','BoxNewsLinkMore','BoxNewsMT','BoxNewsNav','BoxNewsNavLink','BoxNPrzeCzol1','BoxNPrzeCzol10','BoxNPrzeCzol11','BoxNPrzeCzol12','BoxNPrzeCzol13','BoxNPrzeCzol14','BoxNPrzeCzol15','BoxNPrzeCzol16','BoxNPrzeCzol17','BoxNPrzeCzol18','BoxNPrzeCzol19','BoxNPrzeCzol2','BoxNPrzeCzol20','BoxNPrzeCzol21','BoxNPrzeCzol22','BoxNPrzeCzol23','BoxNPrzeCzol24','BoxNPrzeCzol25','BoxNPrzeCzol26','BoxNPrzeCzol27','BoxNPrzeCzol3','BoxNPrzeCzol4','BoxNPrzeCzol5','BoxNPrzeCzol6','BoxNPrzeCzol7','BoxNPrzeCzol8','BoxNPrzeCzol9','BoxOpCzol1','BoxOpCzol2','BoxOpCzol3','BoxOpImg1','BoxOpImg2','BoxOpImg3','BoxOpImg4','BoxOpImg5','BoxOpImg6','BoxOpImg7','BoxOpImg8','BoxOpImg9','BoxOpImg10','BoxOpImg11','BoxOpLink','BoxOpMT','BoxPloCzol1','BoxPloImg1','BoxPloImg2','BoxPloImg3','BoxPloLink','BoxPloLinkImg','BoxPloMT','BoxPloNavLink','BoxRozCzol1','BoxRozImg1','BoxRozImg2','BoxRozImg3','BoxRozImg4','BoxRozMT','BoxRozNavLink','BoxSportImg1','BoxSportImg2','BoxSportImg3','BoxSportLink','BoxSportLinkImg','BoxSportMT','BoxSportNav','BoxWyboImg1','BoxWyboImg2','BoxWyboImg3','BoxWyboImg4','BoxWyboLink','BoxWyboLinkImg','BoxWyboMT','BoxWyboNav','NavMoreSeo','PublioBoxEcom1','PublioBoxEcom2','PublioBoxEcom3','PublioBoxEcom4','NavLinks','NavLinksSm','NavIco','NavIcoSm','BoxOpMT1','BoxOpMT2','BoxOpMT3','BoxOpMT4','BoxPogTer','BoxPogNast','BoxPogJut','BoxPog7','BoxPogMi','BoxSportImg4','BoxSportCzol1','BoxSportLinkMore','BoxBizImg4','BoxBizLinkMore','BoxLSImg3','BoxLSImg4','BoxLSLinkMore','BoxPloNav','BoxPloLinkMore','BoxLSFCzol1','BoxLSFNav','BoxLSFLinkMore','BoxKultLinkMore','BoxRozNav','BoxRozLink','BoxRozLinkMore','BoxWyboNavLink','BoxWyboLinkMore','BoxLoWaNavLink','BoxLoWaLinkMore','BoxLoKrNav','BoxLoKrLinkMore','BoxLoKiNav','BoxLoKiLinkMore','BoxLoBiNav','BoxLoBiNavLink','BoxLoBiLinkMore','BoxLoBbNav','BoxLoBbLinkMore','BoxLoByNav','BoxLoByLinkMore','BoxLoGlMT','BoxLoGlImg1','BoxLoGlImg2','BoxLoGlImg3','BoxLoGlImg4','BoxLoGlLink','BoxLoGlNav','BoxLoGlNavLink','BoxLoGlLinkMore','BoxLoTrLinkMore','BoxLoGwNav','BoxLoGwNavLink','BoxLoGwLinkMore','BoxLoKaNav','BoxLoKaLinkMore','BoxLoLuNav','BoxLoLuLinkMore','BoxLoLoNav','BoxLoLoLinkMore','BoxLoOlMT','BoxLoOlImg1','BoxLoOlImg2','BoxLoOlImg3','BoxLoOlImg4','BoxLoOlLink','BoxLoOlNav','BoxLoOlNavLink','BoxLoOlLinkMore','BoxLoOpNav','BoxLoOpLinkMore','BoxLoPlNav','BoxLoPlNavLink','BoxLoPlLinkMore','BoxLoPoNav','BoxLoPoLinkMore','BoxLoCzLinkMore','BoxLoRaNav','BoxLoRaLinkMore','BoxLoRzMT','BoxLoRzImg1','BoxLoRzNav','BoxLoRzLinkMore','BoxLoSoNav','BoxLoSoLinkMore','BoxLoSzNav','BoxLoSzLinkMore','BoxLoToMT','BoxLoToImg1','BoxLoToImg2','BoxLoToImg3','BoxLoToImg4','BoxLoToLink','BoxLoToNav','BoxLoToNavLink','BoxLoToLinkMore','BoxLoWrNav','BoxLoWrLinkMore','BoxLoZgNav','BoxLoZgLinkMore','BoxLoCpMT','BoxLoCpImg1','BoxLoCpImg2','BoxLoCpImg3','BoxLoCpImg4','BoxLoCpLink','BoxLoCpNav','BoxLoCpNavLink','BoxLoCpLinkMore','BoxC2COp1','BoxC2COp2','BoxC2COp10','BoxC2COp3','BoxC2COp4','BoxC2COp5','BoxC2COp6','BoxC2COp7','BoxC2COp8','BoxC2COp9','BoxC2CLS1','BoxC2CLS10','BoxC2CLS2','BoxC2CLS3','BoxC2CLS4','BoxC2CLS5','BoxC2CLS6','BoxC2CLS7','BoxC2CLS8','BoxC2CLS9','BoxC2CMot1','BoxC2CMot10','BoxC2CMot2','BoxC2CMot3','BoxC2CMot4','BoxC2CMot5','BoxC2CMot6','BoxC2CMot7','BoxC2CMot8','BoxC2CMot9','BoxC2CPlo1','BoxC2CPlo10','BoxC2CPlo2','BoxC2CPlo3','BoxC2CPlo4','BoxC2CPlo5','BoxC2CPlo6','BoxC2CPlo7','BoxC2CPlo8','BoxC2CPlo9','BoxC2CSport1','BoxC2CSport10','BoxC2CSport2','BoxC2CSport3','BoxC2CSport4','BoxC2CSport5','BoxC2CSport6','BoxC2CSport7','BoxC2CSport8','BoxC2CSport9','BoxWeSl_1_1','BoxWeSl_1_2','BoxWeSl_2_1','BoxWeSl_2_2','BoxWeSl_3_1','BoxWeSl_3_2','BoxWeSl_4_1','BoxWeSl_4_2','BoxWeSl_5_1','BoxWeSl_5_2','BoxWeFr','BoxLoBbLinkImg','BoxLoBiLinkImg','BoxLoByLinkImg','BoxLoCzLinkImg','BoxLoGLLinkImg','BoxLoGwLinkImg','BoxLoKaLinkImg','BoxLoKiLinkImg','BoxLoKrLinkImg','BoxLoLoLinkImg','BoxLoLuLinkImg','BoxLoOlsztynLinkImg','BoxLoOpLinkImg','BoxLoPlLinkImg','BoxLoPoLinkImg','BoxLoRaLinkImg','BoxLoRzLinkImg','BoxLoSoLinkImg','BoxLoSzLinkImg','BoxLoTrLinkImg','BoxLoWaLinkImg','BoxLoWrLinkImg','BoxLoZgLinkImg','BoxLoGlLinkImg','BoxLoOlLinkImg','BoxLoToLinkImg','BoxLoCpLinkImg','BoxBizCz','BoxBizImg','BoxDesCz','BoxDesImg','BoxDesNav','BoxDWCz','BoxDWImg','BoxDWNav','BoxLSECom','BoxFiBImg','BoxFiBNav','FiBImg','FiBNav','BoxGWNav','BoxHorNav','BoxIkoBook','BoxIkoGie','BoxIkoGry','BoxIkoHor','BoxIkoKina','BoxIkoLotto','BoxIkoPog','BoxIkoTV','BoxKultImg','BoxLSCz','BoxLSImg','BoxLokKrajImg','BoxLokKrajLink','BoxLokKrajLinkImg','BoxLokBialImg','BoxLokBialLink','BoxLokBialNav','BoxLokBialLinkImg','BoxLokBieImg','BoxLokBieLink','BoxLokBieLinkImg','BoxLokBieNav','BoxLokBydImg','BoxLokBydLink','BoxLokBydLinkImg','BoxLokBydNav','BoxLokCzeImg','BoxLokCzeLink','BoxLokCzeLinkImg','BoxLokCzeNav','BoxLokGlwImg','BoxLokGlwLink','BoxLokGlwLinkImg','BoxLokGorzImg','BoxLokGorzLink','BoxLokGorzLinkImg','BoxLokKatImg','BoxLokKatLink','BoxLokKatLinkImg','BoxLokKatNav','BoxLokKielImg','BoxLokKielLink','BoxLokKielLinkImg','BoxLokKielNav','BoxLokKrakImg','BoxLokKrakLink','BoxLokKrakLinkImg','BoxLokKrakNav','BoxLokLubImg','BoxLokLubLink','BoxLokLubLinkImg','BoxLokLubNav','BoxLokLodzImg','BoxLokLodzLink','BoxLokLodzLinkImg','BoxLokLodzNav','BoxLokOlsImg','BoxLokOlsLink','BoxLokOlsNav','BoxLokOlsLinkImg','BoxLokOpoImg','BoxLokOpoLink','BoxLokOpoLinkImg','BoxLokOpoNav','BoxLokPloImg','BoxLokPloLink','BoxLokPloNav','BoxLokPloLinkImg','BoxLokPozImg','BoxLokPozLink','BoxLokPozNav','BoxLokPozLinkImg','BoxLokRadImg','BoxLokRadLink','BoxLokRadNav','BoxLokRadLinkImg','BoxLokRzeImg','BoxLokRzeLink','BoxLokRzeLinkImg','BoxLokRzeNav','BoxLokSosImg','BoxLokSosLink','BoxLokSosLinkImg','BoxLokSosNav','BoxLokSznImg','BoxLokSznLink','BoxLokSznLinkImg','BoxLokSznNav','BoxLokTorImg','BoxLokTorLink','BoxLokTorLinkImg','BoxLokTorNav','BoxLokTrojImg','BoxLokTrojLink','BoxLokTrojLinkImg','BoxLokTrojNav','BoxLokWawImg','BoxLokWawNav','BoxLokWawLink','BoxLokWawLinkImg','BoxLokWrocImg','BoxLokWrocLink','BoxLokWrocLinkImg','BoxLokWrocNav','BoxLokZielImg','BoxLokZielLink','BoxLokZielLinkImg','BoxLokZielNav','MegaMT','BoxMU2Img','ModaUroda2Img','BoxMUCz','BoxMUImg','BoxMotoImg','BoxMotoNav','MotoImg','MTstream','mBoxNajcz','BoxNiePrzeg','NiePrzeg','BoxOferty','BoxNextImg','Czolka3Img','MT','MT2','Prze','BoxPloCz2','BoxPloCz3','BoxPloPrze','BoxTripImg','BoxTrvCz','BoxTrvImg','BoxTrvNav','PodrozeCz','PodrozeImg','TRPog','BoxPubImg','BoxRekrCz','BoxRekrImg','BoxRekrNav','BoxSpecImg','BoxSpecLink','BoxSpecialNav','BoxSpecial','BoxSportCz','BoxSportImg','BoxSportPrze','Weekend','TRwknd','BoxNewsImg','BoxVidImg','BoxVidLifeImg','BoxVODImg','BoxWN','gospoIco','Img','Ogl_all','Czolka3Img2','Prze2','HatNav','NavLinksCz','HatNavCz','NavMore','TROgl','BoxPubNav','A_BoxBizCzol1','A_BoxBizImg1','A_BoxBizImg2','A_BoxBizImg3','A_BoxBizLink','A_BoxBizLinkImg','A_BoxBizMT','A_BoxBizNav','A_BoxGWImg','A_BoxHor','A_BoxMTPromoImg','A_BoxMTPromo','A_BoxKultIko','A_BoxKultImg1','A_BoxKultImg2','A_BoxKultImg3','A_BoxKultLink','A_BoxKultLinkImg','A_BoxKultNav','A_BoxKultNavLink','A_BoxLoBbImg1','A_BoxLoBbImg2','A_BoxLoBbImg3','A_BoxLoBbImg4','A_BoxLoBbLink','A_BoxLoBbMT','A_BoxLoBbNavLink','A_BoxLoBiImg1','A_BoxLoBiImg2','A_BoxLoBiImg3','A_BoxLoBiImg4','A_BoxLoBiLink','A_BoxLoBiMT','A_BoxLoByImg1','A_BoxLoByImg2','A_BoxLoByImg3','A_BoxLoByImg4','A_BoxLoByLink','A_BoxLoByMT','A_BoxLoByNavLink','A_BoxLoCzImg1','A_BoxLoCzImg2','A_BoxLoCzImg3','A_BoxLoCzImg4','A_BoxLoCzNav','A_BoxLoCzLink','A_BoxLoCzMT','A_BoxLoCzNavLink','A_BoxLoGLImg1','A_BoxLoGLImg2','A_BoxLoGLImg3','A_BoxLoGLImg4','A_BoxLoGLLink','A_BoxLoGLMT','A_BoxLoGLNavLink','A_BoxLoGwImg1','A_BoxLoGwImg2','A_BoxLoGwImg3','A_BoxLoGwImg4','A_BoxLoGwLink','A_BoxLoGwMT','A_BoxLoKaImg1','A_BoxLoKaImg2','A_BoxLoKaImg3','A_BoxLoKaImg4','A_BoxLoKaLink','A_BoxLoKaMT','A_BoxLoKaNavLink','A_BoxLoKiImg1','A_BoxLoKiImg2','A_BoxLoKiImg3','A_BoxLoKiImg4','A_BoxLoKiLink','A_BoxLoKiMT','A_BoxLoKiNavLink','A_BoxLoKrImg1','A_BoxLoKrImg2','A_BoxLoKrImg3','A_BoxLoKrImg4','A_BoxLoKrLink','A_BoxLoKrMT','A_BoxLoKrNavLink','A_BoxLoLoImg1','A_BoxLoLoImg2','A_BoxLoLoImg3','A_BoxLoLoImg4','A_BoxLoLoLink','A_BoxLoLoMT','A_BoxLoLoNavLink','A_BoxLoLuImg1','A_BoxLoLuImg2','A_BoxLoLuImg3','A_BoxLoLuImg4','A_BoxLoLuLink','A_BoxLoLuMT','A_BoxLoLuNavLink','A_BoxLoOlsztynImg1','A_BoxLoOlsztynImg2','A_BoxLoOlsztynImg3','A_BoxLoOlsztynImg4','A_BoxLoOlsztynLink','A_BoxLoOlsztynMT','A_BoxLoOlsztynNavLink','A_BoxLoOlsztynNav','A_BoxLoOpImg1','A_BoxLoOpImg2','A_BoxLoOpImg3','A_BoxLoOpImg4','A_BoxLoOpLink','A_BoxLoOpMT','A_BoxLoOpNavLink','A_BoxLoPlImg1','A_BoxLoPlImg2','A_BoxLoPlImg3','A_BoxLoPlImg4','A_BoxLoPlLink','A_BoxLoPlMT','A_BoxLoPoImg1','A_BoxLoPoImg2','A_BoxLoPoImg3','A_BoxLoPoImg4','A_BoxLoPoLink','A_BoxLoPoMT','A_BoxLoPoNavLink','A_BoxLoRaImg1','A_BoxLoRaImg2','A_BoxLoRaImg3','A_BoxLoRaImg4','A_BoxLoRaLink','A_BoxLoRaMT','A_BoxLoRaNavLink','A_BoxLoRzImg2','A_BoxLoRzImg3','A_BoxLoRzImg4','A_BoxLoRzLink','A_BoxLoRzNavLink','A_BoxLoSoImg1','A_BoxLoSoImg2','A_BoxLoSoImg3','A_BoxLoSoImg4','A_BoxLoSoLink','A_BoxLoSoMT','A_BoxLoSoNavLink','A_BoxLoSzImg1','A_BoxLoSzImg2','A_BoxLoSzImg3','A_BoxLoSzImg4','A_BoxLoSzLink','A_BoxLoSzMT','A_BoxLoSzNavLink','A_BoxLoTrImg1','A_BoxLoTrImg2','A_BoxLoTrImg3','A_BoxLoTrImg4','A_BoxLoTrLink','A_BoxLoTrMT','A_BoxLoTrNavLink','A_BoxLoTrNav','A_BoxLoWaImg1','A_BoxLoWaImg2','A_BoxLoWaImg3','A_BoxLoWaImg4','A_BoxLoWaLink','A_BoxLoWaMT','A_BoxLoWaNav','A_BoxLoWrImg1','A_BoxLoWrImg2','A_BoxLoWrImg3','A_BoxLoWrImg4','A_BoxLoWrLink','A_BoxLoWrMT','A_BoxLoWrNavLink','A_BoxLoZgImg1','A_BoxLoZgImg2','A_BoxLoZgImg3','A_BoxLoZgImg4','A_BoxLoZgLink','A_BoxLoZgMT','A_BoxLoZgNavLink','A_BoxLSCzol1','A_BoxLSCzol2','A_BoxMotMT','A_BoxMotImg1','A_BoxMotImg2','A_BoxMotImg3','A_BoxMotImg4','A_BoxMotLink','A_BoxMotLinkImg','A_BoxMotNav','A_BoxLSFCZOL1','A_BoxLSFImg1','A_BoxLSFImg2','A_BoxLSFImg3','A_BoxLSFImg4','A_BoxLSFLink','A_BoxLSFMT','A_BoxLSImg1','A_BoxLSImg2','A_BoxLSLink','A_BoxLSLinkImg','A_BoxLSMT','A_BoxLSNav','A_ECON','A_BoxNewestNav','A_BoxNewsImg1','A_BoxNewsImg2','A_BoxNewsImg3','A_BoxNewsImg4','A_BoxNewsLink','A_BoxNewsLinkImg','A_BoxNewsLinkMore','A_BoxNewsMT','A_BoxNewsNav','A_BoxNewsNavLink','A_BoxNPrzeCzol1','A_BoxNPrzeCzol10','A_BoxNPrzeCzol11','A_BoxNPrzeCzol12','A_BoxNPrzeCzol13','A_BoxNPrzeCzol14','A_BoxNPrzeCzol15','A_BoxNPrzeCzol16','A_BoxNPrzeCzol17','A_BoxNPrzeCzol18','A_BoxNPrzeCzol19','A_BoxNPrzeCzol2','A_BoxNPrzeCzol20','A_BoxNPrzeCzol21','A_BoxNPrzeCzol22','A_BoxNPrzeCzol23','A_BoxNPrzeCzol24','A_BoxNPrzeCzol25','A_BoxNPrzeCzol26','A_BoxNPrzeCzol27','A_BoxNPrzeCzol3','A_BoxNPrzeCzol4','A_BoxNPrzeCzol5','A_BoxNPrzeCzol6','A_BoxNPrzeCzol7','A_BoxNPrzeCzol8','A_BoxNPrzeCzol9','A_BoxOpCzol1','A_BoxOpCzol2','A_BoxOpCzol3','A_BoxOpImg1','A_BoxOpImg2','A_BoxOpImg3','A_BoxOpImg4','A_BoxOpImg5','A_BoxOpImg6','A_BoxOpImg7','A_BoxOpImg8','A_BoxOpImg9','A_BoxOpImg10','A_BoxOpImg11','A_BoxOpLink','A_BoxOpMT','A_BoxPloCzol1','A_BoxPloImg1','A_BoxPloImg2','A_BoxPloImg3','A_BoxPloLink','A_BoxPloLinkImg','A_BoxPloMT','A_BoxPloNavLink','A_BoxRozCzol1','A_BoxRozImg1','A_BoxRozImg2','A_BoxRozImg3','A_BoxRozImg4','A_BoxRozMT','A_BoxRozNavLink','A_BoxSportImg1','A_BoxSportImg2','A_BoxSportImg3','A_BoxSportLink','A_BoxSportLinkImg','A_BoxSportMT','A_BoxSportNav','A_BoxWyboImg1','A_BoxWyboImg2','A_BoxWyboImg3','A_BoxWyboImg4','A_BoxWyboLink','A_BoxWyboLinkImg','A_BoxWyboMT','A_BoxWyboNav','A_NavMoreSeo','A_PublioBoxEcom1','A_PublioBoxEcom2','A_PublioBoxEcom3','A_PublioBoxEcom4','A_NavLinks','A_NavLinksSm','A_NavIco','A_NavIcoSm','A_BoxOpMT1','A_BoxOpMT2','A_BoxOpMT3','A_BoxOpMT4','A_BoxPogTer','A_BoxPogNast','A_BoxPogJut','A_BoxPog7','A_BoxPogMi','A_BoxSportImg4','A_BoxSportCzol1','A_BoxSportLinkMore','A_BoxBizImg4','A_BoxBizLinkMore','A_BoxLSImg3','A_BoxLSImg4','A_BoxLSLinkMore','A_BoxPloNav','A_BoxPloLinkMore','A_BoxLSFCzol1','A_BoxLSFNav','A_BoxLSFLinkMore','A_BoxKultLinkMore','A_BoxRozNav','A_BoxRozLink','A_BoxRozLinkMore','A_BoxWyboNavLink','A_BoxWyboLinkMore','A_BoxLoWaNavLink','A_BoxLoWaLinkMore','A_BoxLoKrNav','A_BoxLoKrLinkMore','A_BoxLoKiNav','A_BoxLoKiLinkMore','A_BoxLoBiNav','A_BoxLoBiNavLink','A_BoxLoBiLinkMore','A_BoxLoBbNav','A_BoxLoBbLinkMore','A_BoxLoByNav','A_BoxLoByLinkMore','A_BoxLoGlMT','A_BoxLoGlImg1','A_BoxLoGlImg2','A_BoxLoGlImg3','A_BoxLoGlImg4','A_BoxLoGlLink','A_BoxLoGlNav','A_BoxLoGlNavLink','A_BoxLoGlLinkMore','A_BoxLoTrLinkMore','A_BoxLoGwNav','A_BoxLoGwNavLink','A_BoxLoGwLinkMore','A_BoxLoKaNav','A_BoxLoKaLinkMore','A_BoxLoLuNav','A_BoxLoLuLinkMore','A_BoxLoLoNav','A_BoxLoLoLinkMore','A_BoxLoOlMT','A_BoxLoOlImg1','A_BoxLoOlImg2','A_BoxLoOlImg3','A_BoxLoOlImg4','A_BoxLoOlLink','A_BoxLoOlNav','A_BoxLoOlNavLink','A_BoxLoOlLinkMore','A_BoxLoOpNav','A_BoxLoOpLinkMore','A_BoxLoPlNav','A_BoxLoPlNavLink','A_BoxLoPlLinkMore','A_BoxLoPoNav','A_BoxLoPoLinkMore','A_BoxLoCzLinkMore','A_BoxLoRaNav','A_BoxLoRaLinkMore','A_BoxLoRzMT','A_BoxLoRzImg1','A_BoxLoRzNav','A_BoxLoRzLinkMore','A_BoxLoSoNav','A_BoxLoSoLinkMore','A_BoxLoSzNav','A_BoxLoSzLinkMore','A_BoxLoToMT','A_BoxLoToImg1','A_BoxLoToImg2','A_BoxLoToImg3','A_BoxLoToImg4','A_BoxLoToLink','A_BoxLoToNav','A_BoxLoToNavLink','A_BoxLoToLinkMore','A_BoxLoWrNav','A_BoxLoWrLinkMore','A_BoxLoZgNav','A_BoxLoZgLinkMore','A_BoxLoCpMT','A_BoxLoCpImg1','A_BoxLoCpImg2','A_BoxLoCpImg3','A_BoxLoCpImg4','A_BoxLoCpLink','A_BoxLoCpNav','A_BoxLoCpNavLink','A_BoxLoCpLinkMore','A_BoxC2COp1','A_BoxC2COp2','A_BoxC2COp10','A_BoxC2COp3','A_BoxC2COp4','A_BoxC2COp5','A_BoxC2COp6','A_BoxC2COp7','A_BoxC2COp8','A_BoxC2COp9','A_BoxC2CLS1','A_BoxC2CLS10','A_BoxC2CLS2','A_BoxC2CLS3','A_BoxC2CLS4','A_BoxC2CLS5','A_BoxC2CLS6','A_BoxC2CLS7','A_BoxC2CLS8','A_BoxC2CLS9','A_BoxC2CMot1','A_BoxC2CMot10','A_BoxC2CMot2','A_BoxC2CMot3','A_BoxC2CMot4','A_BoxC2CMot5','A_BoxC2CMot6','A_BoxC2CMot7','A_BoxC2CMot8','A_BoxC2CMot9','A_BoxC2CPlo1','A_BoxC2CPlo10','A_BoxC2CPlo2','A_BoxC2CPlo3','A_BoxC2CPlo4','A_BoxC2CPlo5','A_BoxC2CPlo6','A_BoxC2CPlo7','A_BoxC2CPlo8','A_BoxC2CPlo9','A_BoxC2CSport1','A_BoxC2CSport10','A_BoxC2CSport2','A_BoxC2CSport3','A_BoxC2CSport4','A_BoxC2CSport5','A_BoxC2CSport6','A_BoxC2CSport7','A_BoxC2CSport8','A_BoxC2CSport9','A_BoxWeSl_1_1','A_BoxWeSl_1_2','A_BoxWeSl_2_1','A_BoxWeSl_2_2','A_BoxWeSl_3_1','A_BoxWeSl_3_2','A_BoxWeSl_4_1','A_BoxWeSl_4_2','A_BoxWeSl_5_1','A_BoxWeSl_5_2','A_BoxWeFr','A_BoxLoBbLinkImg','A_BoxLoBiLinkImg','A_BoxLoByLinkImg','A_BoxLoCzLinkImg','A_BoxLoGLLinkImg','A_BoxLoGwLinkImg','A_BoxLoKaLinkImg','A_BoxLoKiLinkImg','A_BoxLoKrLinkImg','A_BoxLoLoLinkImg','A_BoxLoLuLinkImg','A_BoxLoOlsztynLinkImg','A_BoxLoOpLinkImg','A_BoxLoPlLinkImg','A_BoxLoPoLinkImg','A_BoxLoRaLinkImg','A_BoxLoRzLinkImg','A_BoxLoSoLinkImg','A_BoxLoSzLinkImg','A_BoxLoTrLinkImg','A_BoxLoWaLinkImg','A_BoxLoWrLinkImg','A_BoxLoZgLinkImg','A_BoxLoGlLinkImg','A_BoxLoOlLinkImg','A_BoxLoToLinkImg','A_BoxLoCpLinkImg','Z_BoxBizCz','Z_BoxBizImg','Z_BoxBizLink','Z_BoxBizLinkImg','Z_BoxBizNav','Z_BoxDesCz','Z_BoxDesImg','Z_BoxDesNav','Z_BoxDWCz','Z_BoxDWImg','Z_BoxDWNav','Z_BoxLSECom','Z_BoxFiBImg','Z_BoxFiBNav','Z_FiBImg','Z_FiBNav','Z_BoxGWImg','Z_BoxGWNav','Z_BoxHor','Z_BoxHorNav','Z_BoxIkoBook','Z_BoxIkoGie','Z_BoxIkoGry','Z_BoxIkoHor','Z_BoxIkoKina','Z_BoxIkoLotto','Z_BoxIkoPog','Z_BoxIkoTV','Z_BoxKultImg','Z_BoxKultNav','Z_BoxLSCz','Z_BoxLSImg','Z_BoxLSLink','Z_BoxLSLinkImg','Z_BoxLSNav','Z_BoxLokKrajImg','Z_BoxLokKrajLink','Z_BoxLokKrajLinkImg','Z_BoxLokBialImg','Z_BoxLokBialLink','Z_BoxLokBialNav','Z_BoxLokBialLinkImg','Z_BoxLokBieImg','Z_BoxLokBieLink','Z_BoxLokBieLinkImg','Z_BoxLokBieNav','Z_BoxLokBydImg','Z_BoxLokBydLink','Z_BoxLokBydLinkImg','Z_BoxLokBydNav','Z_BoxLokCzeImg','Z_BoxLokCzeLink','Z_BoxLokCzeLinkImg','Z_BoxLokCzeNav','Z_BoxLokGlwImg','Z_BoxLokGlwLink','Z_BoxLokGlwLinkImg','Z_BoxLokGorzImg','Z_BoxLokGorzLink','Z_BoxLokGorzLinkImg','Z_BoxLokKatImg','Z_BoxLokKatLink','Z_BoxLokKatLinkImg','Z_BoxLokKatNav','Z_BoxLokKielImg','Z_BoxLokKielLink','Z_BoxLokKielLinkImg','Z_BoxLokKielNav','Z_BoxLokKrakImg','Z_BoxLokKrakLink','Z_BoxLokKrakLinkImg','Z_BoxLokKrakNav','Z_BoxLokLubImg','Z_BoxLokLubLink','Z_BoxLokLubLinkImg','Z_BoxLokLubNav','Z_BoxLokLodzImg','Z_BoxLokLodzLink','Z_BoxLokLodzLinkImg','Z_BoxLokLodzNav','Z_BoxLokOlsImg','Z_BoxLokOlsLink','Z_BoxLokOlsNav','Z_BoxLokOlsLinkImg','Z_BoxLokOpoImg','Z_BoxLokOpoLink','Z_BoxLokOpoLinkImg','Z_BoxLokOpoNav','Z_BoxLokPloImg','Z_BoxLokPloLink','Z_BoxLokPloNav','Z_BoxLokPloLinkImg','Z_BoxLokPozImg','Z_BoxLokPozLink','Z_BoxLokPozNav','Z_BoxLokPozLinkImg','Z_BoxLokRadImg','Z_BoxLokRadLink','Z_BoxLokRadNav','Z_BoxLokRadLinkImg','Z_BoxLokRzeImg','Z_BoxLokRzeLink','Z_BoxLokRzeLinkImg','Z_BoxLokRzeNav','Z_BoxLokSosImg','Z_BoxLokSosLink','Z_BoxLokSosLinkImg','Z_BoxLokSosNav','Z_BoxLokSznImg','Z_BoxLokSznLink','Z_BoxLokSznLinkImg','Z_BoxLokSznNav','Z_BoxLokTorImg','Z_BoxLokTorLink','Z_BoxLokTorLinkImg','Z_BoxLokTorNav','Z_BoxLokTrojImg','Z_BoxLokTrojLink','Z_BoxLokTrojLinkImg','Z_BoxLokTrojNav','Z_BoxLokWawImg','Z_BoxLokWawNav','Z_BoxLokWawLink','Z_BoxLokWawLinkImg','Z_BoxLokWrocImg','Z_BoxLokWrocLink','Z_BoxLokWrocLinkImg','Z_BoxLokWrocNav','Z_BoxLokZielImg','Z_BoxLokZielLink','Z_BoxLokZielLinkImg','Z_BoxLokZielNav','Z_MegaMT','Z_BoxMU2Img','Z_ModaUroda2Img','Z_BoxMUCz','Z_BoxMUImg','Z_BoxMotoImg','Z_BoxMotoNav','Z_MotoImg','Z_BoxMTPromoImg','Z_BoxMTPromo','Z_MTstream','Z_mBoxNajcz','Z_BoxNiePrzeg','Z_NiePrzeg','Z_BoxOferty','Z_BoxNextImg','Z_Czolka3Img','Z_MT','Z_MT2','Z_Prze','Z_BoxPloCz2','Z_BoxPloCz3','Z_BoxPloMT','Z_BoxPloPrze','Z_BoxPloNav','Z_BoxSportNav','Z_BoxTripImg','Z_BoxTrvCz','Z_BoxTrvImg','Z_BoxTrvNav','Z_PodrozeCz','Z_PodrozeImg','Z_BoxPogJut','Z_BoxPogMi','Z_BoxPogTer','Z_TRPog','Z_BoxPubImg','Z_BoxRekrCz','Z_BoxRekrImg','Z_BoxRekrNav','Z_BoxSpecImg','Z_BoxSpecLink','Z_BoxSpecialNav','Z_BoxSpecial','Z_BoxSportCz','Z_BoxSportImg','Z_BoxSportLink','Z_BoxSportLinkImg','Z_BoxSportPrze','Z_Weekend','Z_TRwknd','Z_BoxNewsImg','Z_BoxNewsNav','Z_BoxNewsLink','Z_BoxNewsLinkImg','Z_BoxVidImg','Z_BoxVidLifeImg','Z_BoxVODImg','Z_BoxWN','Z_gospoIco','Z_Img','Z_Ogl_all','Z_Czolka3Img2','Z_Prze2','Z_NavLinks','Z_HatNav','Z_NavLinksCz','Z_HatNavCz','Z_NavIco','Z_NavMore','Z_TROgl','Z_BoxPubNav','BoxMTPPush','Z_BoxMTPPush','A_BoxMTPPush','BoxMTPAuto','A_BoxMTPAuto','Z_BoxMTPAuto'];  + + + + +if (( !document.referrer && (window.location.host === 'www.gazeta.pl' || window.location.host === 'm.gazeta.pl') && +    (window.location.href.indexOf('0,0.html?p=') !== -1 || window.location.href.indexOf('0,0.html?promocja=') !== -1)) ||  +    ((document.referrer.indexOf('www.gazeta.pl') !== -1 || document.referrer.indexOf('m.gazeta.pl') !== -1) &&  +     (document.referrer.indexOf('0,0.html?p=') !== -1 || document.referrer.indexOf('0,0.html?promocja=') !== -1))) { + +    var hashTag = ''; +    if (window.location.hash) { +        var +            hash = window.location.hash, +            n = hash.indexOf('&'); +        hash = hash.substring(0, n != -1 ? n : hash.length); +        var getHash = hash.split('#').pop(); +        if (hashArrDFP.indexOf(getHash) !== -1) { hashTag = '_' + getHash; } +    } + +    googletag.pubads().setTargeting('src','HPGAZETA-DHP' + hashTag); +    _setCookieDFP('HPGAZETA-DHP' + hashTag); +    if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("src|HPGAZETA-DHP" + hashTag); + +} else if ( document.referrer.split("?")[0].indexOf('facebook.com') !== -1 || +    ( !document.referrer && document.location.href.indexOf('utm_source=facebook.com') !== -1 ) ) { + +    if (window.location.href.indexOf('utm_content') !== -1 || document.referrer.indexOf('utm_content') !== -1) { +     +        var paramsObject = function ( name, url ) { +            if (!url) url = location.href; +                name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]"); +                var regexS = "[\\?&]"+name+"=([^&#]*)"; +                var regex = new RegExp( regexS ); +                var results = regex.exec( url ); +                return results == null ? null : results[1]; +            }, +            checkHrefDFP = paramsObject('utm_content', window.location.href), +            checkReferrerDFP = paramsObject('utm_content', document.referrer), +            newValueDFP = checkHrefDFP || checkReferrerDFP; + +        googletag.pubads().setTargeting('src', 'FACEBOOK_CPC_' + newValueDFP); +        _setCookieDFP('FACEBOOK_CPC_' + newValueDFP); +        if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("src|FACEBOOK_CPC_" + newValueDFP); + +    } else { +        googletag.pubads().setTargeting('src','FACEBOOK'); +        _setCookieDFP('FACEBOOK'); +        if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("src|FACEBOOK"); +    } + + +} else if ( ( document.location.href.indexOf('utm_medium=cpc') !== -1 && document.location.href.indexOf('utm_source=facebook.com') === -1) || +    (document.location.href.indexOf('adw=1') !== -1 || document.location.href.indexOf('gclid=') !== -1) || +    (document.referrer.indexOf('googleads.g.doubleclick.net') !== -1 || document.referrer.indexOf('googleadservices.com') !== -1) ) { + +    googletag.pubads().setTargeting('src','CPC'); +    _setCookieDFP('CPC'); +    if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("src|CPC"); + +} else if( (document.location.href.indexOf('www.gazeta.pl') !== -1 || document.location.href.indexOf('m.gazeta.pl') !== -1) || +    (document.referrer.indexOf('www.gazeta.pl') !== -1 || document.referrer.indexOf('m.gazeta.pl') !== -1) ) { + +    var hashTag = ''; +    if (window.location.hash) { +        var +            hash = window.location.hash, +            n = hash.indexOf('&'); +        hash = hash.substring(0, n != -1 ? n : hash.length); +        var getHash = hash.split('#').pop(); +        if (hashArrDFP.indexOf(getHash) !== -1) { hashTag = '_' + getHash; } +    } + +    googletag.pubads().setTargeting('src','HPGAZETA' + hashTag); +    _setCookieDFP('HPGAZETA' + hashTag); +    if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("src|HPGAZETA" + hashTag); + +} else if (!document.referrer) { + +    googletag.pubads().setTargeting('src','DIRECT'); +    _setCookieDFP('DIRECT'); +    if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("src|DIRECT"); + +} else if ( (document.referrer.indexOf('szukaj.gazeta.pl') === -1) && +    (document.referrer.indexOf('?q=') !== -1 || document.referrer.indexOf('&q=') !== -1 || document.referrer.indexOf('?query=') !== -1 || document.referrer.indexOf('search.yahoo.com') !== -1 || document.referrer.indexOf('&query=') !== -1 || document.referrer.indexOf('?szukaj=') !== -1 || document.referrer.indexOf('&szukaj') !== -1 || (document.referrer.indexOf('//www.google.') !== -1 && document.referrer.indexOf('logout') === -1)) && +    document.location.href.indexOf('0,0.html') !== -1 || window.location.href.slice(-3) === '.pl' || window.location.href.slice(-4) === '.biz' || window.location.href.slice(-3) === '.tv' || window.location.href.slice(-8) === '.pl/html') { + +    googletag.pubads().setTargeting('src','DIRECT-SEO'); +    _setCookieDFP('DIRECT-SEO'); +    if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("src|DIRECT-SEO"); + +} else if ( document.referrer.indexOf('szukaj.gazeta.pl') === -1 && +    (document.referrer.indexOf('?q=') !== -1 || document.referrer.indexOf('&q=') !== -1 || document.referrer.indexOf('?query=') !== -1 || document.referrer.indexOf('search.yahoo.com') !== -1 || document.referrer.indexOf('&query=') !== -1 || document.referrer.indexOf('?szukaj=') !== -1 || document.referrer.indexOf('&szukaj') !== -1 || (document.referrer.indexOf('//www.google.') !== -1 && document.referrer.indexOf('logout') === -1)) ) { + +    googletag.pubads().setTargeting('src','SEO'); +    _setCookieDFP('SEO'); +    if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("src|SEO"); + +} else if (cookieIfr) { + +    googletag.pubads().setTargeting('src', cookieIfr); +    if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("src|" + cookieIfr); + +}  + + +if ((window.location.host == "www.gazeta.pl") || (window.location.host == "m.gazeta.pl")) { +	if (window.location.href.indexOf('pushpushgo')!== -1 && window.location.href.indexOf('mtp=')!== -1) { +		googletag.pubads().setTargeting('test','MTP_PUSH'); +		if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("test|MTP_PUSH"); +	} else if ((window.location.href.indexOf('utm_source=facebook')!== -1 && window.location.href.indexOf('mtp=')!== -1) || window.location.href.indexOf('boxmtpautosp=')!== -1) { +		googletag.pubads().setTargeting('test','MTP_FBCPC'); +		if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("test|MTP_FBCPC"); +	} else if (window.location.href.indexOf('boxmtpauto=')!== -1) { +		googletag.pubads().setTargeting('test','MTP_FBORG'); +		if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("test|MTP_FBORG"); +	} else if ((window.location.href.indexOf('?p=')!== -1 || window.location.href.indexOf('?promocja=')!== -1) && document.referrer == "") { +		googletag.pubads().setTargeting('test','MTP_DHP'); +		if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("test|MTP_DHP"); +	} else if (window.location.href.indexOf('utm_campaign=amtp')!== -1) { +		googletag.pubads().setTargeting('test','MTP_AMTP'); +		if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("test|MTP_AMTP4"); +	} else if (window.location.href.indexOf('?404')!== -1) { +		googletag.pubads().setTargeting('test','MTP_404'); +		if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("test|MTP_404"); +	}else { +		googletag.pubads().setTargeting('test','MTP_NULL'); +		if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("test|MTP_NULL"); +	} +} + +			 +		 +			 +		 +			 +				// [HB] Core-prod-3 - AFTER_SLOTS +if(window.yb_seg && yb_seg!='') { +    googletag.pubads().setTargeting('yb_seg', yb_seg); +    if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("yb_seg|" + yb_seg); +} +if(window.pbjs_yb_hb) { +    googletag.pubads().getSlots().forEach(function(slot){ +        slot.setTargeting('yb_hb', String(yb_hb)); +    }); +    if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("yb_hb|" + String(yb_hb)); +} +if(window.pbjs) pbjs.que.push(function() { +    pbjs.setTargetingForGPTAsync(); +}); +			 +		 +			 +		 +			 +		 +			 +				var yb_ff = String(Math.round(Math.random()));  +googletag.pubads().setTargeting('yb_ff', yb_ff); +var _yt=new Date(),yb_th=_yt.getUTCHours()-8,yb_tm=_yt.getUTCMinutes(),yb_wd=_yt.getUTCDay();  +if(yb_th<0){yb_th=24+yb_th;yb_wd-=1;};if(yb_wd<0){yb_wd=7+yb_wd};  +googletag.pubads().setTargeting('yb_th', yb_th.toString());  +googletag.pubads().setTargeting('yb_tm', yb_tm.toString());  +googletag.pubads().setTargeting('yb_wd', yb_wd.toString());  + +if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("yb_th|" + yb_th.toString()); +if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("yb_tm|" + yb_tm.toString()); +if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push("yb_wd|" + yb_wd.toString()); + + +adviewDFP.getQueryParams = function(e){ +    var n = new RegExp("[\\?&]"+e+"=([^&#]*)").exec(window.location.href); +    return null !== n ? n[1] : null +}; + + +if (adviewDFP.getQueryParams('dfp_target_kw')) adviewDFP.DFPTargeting.push('kw|'+adviewDFP.getQueryParams('dfp_target_kw')); + +var +    kwString = '', +    paramString = ''; + +for(var i = 0; i < adviewDFP.DFPTargeting.length; i++) { +    var +        _this = adviewDFP.DFPTargeting[i], +        _thisParam = _this.split('|'); + +    if (_this.indexOf('kw') !== -1) { +        if (kwString.indexOf(_thisParam[1]) === -1) kwString += '%2C' + _thisParam[1]; +    } else { +        paramString += '%26'+_thisParam[0]+'%3D'+_thisParam[1]; +    } +} + +//_YB +var _YB = window.top._YB || { ab:function(){return (Math.random()>=0.1?"b":"a"+Math.floor(Math.random()*10));} }; + +paramString += '%26yb_ab%3D' + _YB.ab(); +//_YB +paramString += '%26yb_ff%3D' + String(Math.round(Math.random())); + +if (typeof activeSubscription !== 'undefined' && activeSubscription) { +    paramString += '%26subscription%3Dtrue'; +} +   +if (dfpParams && dfpParams.video && dfpParams.video.preroll) { +    dfpParams.video.preroll = dfpParams.video.preroll.replace('%26dystrybutor', kwString + paramString + '%26dystrybutor'); +    dfpParams.video.preroll = dfpParams.video.preroll.replace('[adview_hostname]', escape(location.host)); +    dfpParams.video.preroll = dfpParams.video.preroll.replace('[cb]', window.AG.rodoAccepted); +    dfpParams.video.preroll = dfpParams.video.preroll.replace('[locationhref]', escape(window.location.href)); +}  + +if(dfpParams && dfpParams.video && dfpParams.video.skin) dfpParams.video.skin = dfpParams.video.skin.replace('%26dystrybutor', kwString + paramString + '%26dystrybutor'); +if(dfpParams && dfpParams.video && dfpParams.video.overlay) dfpParams.video.overlay = dfpParams.video.overlay.replace('%26dystrybutor', kwString + paramString + '%26dystrybutor'); +if(dfpParams && dfpParams.video && dfpParams.video.pausead) dfpParams.video.pausead = dfpParams.video.pausead.replace('%26dystrybutor', kwString + paramString + '%26dystrybutor'); +			 +		 +			 +		 +			 +		 +			 +		 +			 +				if (typeof randRodoAB !== 'undefined') { +    googletag.pubads().setTargeting("kw", "testyab_rodo_" + randRodoAB); +    adviewDFP.DFPTargeting.push('kw|testyab_rodo_' + randRodoAB); +} + +(function(){ +    var _pm = window.location.search.match(/\?i=([0-9]+)/); +    if (window.adviewDFP && adviewDFP.DFPTargeting && _pm) adviewDFP.DFPTargeting.push("yb_page|" + String(_pm[1])); +    if (_pm) return googletag.pubads().setTargeting('yb_page', String(_pm[1])); +})(); + +googletag.pubads().setTargeting('domena', ""+window.top.location.hostname+""); + +var +	adviewKW = [];  + +if(typeof window.gExVariation !== 'undefined') { +	adviewKW.push('testyab_' + gExVariation); +    if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push('kw|testyab_' + gExVariation); +} +if (/\/2,|\/14,/.test(window.location.href)) { +	adviewKW.push('relacja'); +    if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push('kw|relacja'); +} +if (/\/15,/.test(window.location.href)) { +	adviewKW.push('quiz-new'); +    if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push('kw|quiz-new'); +} +if (/\/13,/.test(window.location.href)) { +	adviewKW.push('quiz-old'); +    if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push('kw|quiz-old'); +} +if (/\/5,/.test(window.location.href)) { +	adviewKW.push('jsp-5'); +    if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push('kw|jsp-5'); +} +if (/\/51,/.test(window.location.href)) { +	adviewKW.push('jsp-51'); +    if (window.adviewDFP && adviewDFP.DFPTargeting) adviewDFP.DFPTargeting.push('kw|jsp-51'); +} +if (typeof adviewKW !== 'undefined' && adviewKW.length) { +	googletag.pubads().setTargeting('kw', adviewKW); +} + + +			 +		 +			 +		 +			 +		 +			 +		 +			 +		 +			 +		 +			 +		 +			 +		 +			 +		 + +		if(window.AG.rodoAccepted === -1) { +			googletag.pubads().setRequestNonPersonalizedAds(1); +		} + +		googletag.pubads().enableSingleRequest(); +		googletag.pubads().enableSyncRendering(); +		googletag.pubads().collapseEmptyDivs(); +		<!-- IBA Analytics --> +		googletag.pubads().addEventListener('slotRenderEnded', function(data) { +		    adviewDFP.adviewRendered(data); +			if(typeof IBA != 'undefined' && typeof IBA.dfp != 'undefined') IBA.dfp.onSlotRendered(data); +		}); +		if(window.AG.rodoAccepted !== 0) { +		   googletag.enableServices(); +		} +		 +			 +		 +			 +		 +			 +		 +			 +		 +			 +		 +			 +		 +			 +		 +			 +		 +			 +		 +			 +		 +			 +		 +			 +		 +			 +		 +			 +		 +			 +		 +			 +		 +			 +		 +			 +		 +			 +		 +			 +		 + +	}); + +    function putBanDFP(a,b,c,d){if(!b||"string"!=typeof b)return!1;if(!dfpParams.slots[a])return console.warn("dfpParams slot: "+a+" not exists"),!1;var e=window.location.protocol+"//"+window.location.host+"/dfptools/adview/",f=e+dfpParams.slots[a].url;c&&"object"==typeof c&&(f=f.replace(/adUnitSize=\[\[(.*?)\]]/g,"adUnitSize="+JSON.stringify(c))),d&&"string"==typeof d&&(f+="&addKeysKw="+d),adviewDFP.iframeBan(b,f,e+"autoslot.js",0,0)}function putBanDFPInView(a,b,c,d){var e=parseInt(c)?parseInt(c):300;adviewDFP.DFPTargeting.push("kw|oldScroll"),window.addEventListener("scroll",function(){adviewDFP.isElementInViewport(b,e)&&(putBanDFP(a,b,d),this.removeEventListener("scroll",arguments.callee,!1))})}function putBanDFPInViewObject(a){if(!a.slot||!a.divId)return a.slot||console.warn("Slot ID is empty"),a.divId||console.warn("divId id empty"),!1;"021-IMK"!==a.slot&&"067-RECTANGLE-BTF"!==a.slot&&"035-RECTANGLE-BTF"!==a.slot||(a.margin=parseInt(adviewDFP.randomMargin),a.kw?a.kw+=", margin_"+adviewDFP.randomMargin:a.kw="margin_"+adviewDFP.randomMargin);var b=parseInt(a.margin)?parseInt(a.margin):300;window.addEventListener("scroll",function(){adviewDFP.isElementInViewport(a.divId,b)&&(putBanDFP(a.slot,a.divId,a.unitSize,a.kw),this.removeEventListener("scroll",arguments.callee,!1))})}function putBan(){}var adviewDFP=adviewDFP||{};adviewDFP.scrollSlot=[],adviewDFP.DFPTargeting=adviewDFP.DFPTargeting||[],adviewDFP.allSlotsForRODO=[],adviewDFP.arrayLabel=["001-TOPBOARD","003-RECTANGLE","000-MAINBOARD","042-FOOTBOARD","087-ADBOARD-A","071-WINIETA","004-PAYPER","007-CONTENTBOARD","021-IMK","059-BUTTON","078-STYLBOARD","044-BIZBOARD","035-RECTANGLE-BTF","067-RECTANGLE-BTF","101-TOPBOARD-MOBI","104-RECTANGLE-MOBI","107-MAINBOARD-MOBI","150-BIZBOARD-MOBI","151-FUNBOARD-MOBI","152-STYLBOARD-MOBI","153-MOTOBOARD-MOBI","150-ADBOARD-A-MOBI","150-ADBOARD-B-MOBI","150-ADBOARD-C-MOBI","150-ADBOARD-D-MOBI","108-FOOTBOARD-MOBI","000-SPORTBOARD","076-MAINBUTTON-0","111-BIZBUTTON","076-MAINBUTTON","116-SPORTBUTTON"],adviewDFP.check=!0,adviewDFP.init=function(){var a=setInterval(function(){if("complete"===document.readyState){clearInterval(a);var b=document.getElementsByClassName("activeBan"),c=document.getElementsByTagName("body")[0];-1!==window.navigator.userAgent.indexOf("Firefox")&&adviewDFP.firefox(),adviewDFP.check&&adviewDFP.checkAgain(),adviewDFP.checkBan(),adviewDFP.adBlock();for(var d=0;d<b.length;d++){var e=b[d],f=[parseInt(e.getAttribute("data-ad-width")),parseInt(e.getAttribute("data-ad-height"))];if(1===f[0]&&(f=[e.scrollWidth,e.scrollHeight]),"007-CONTENTBOARD"!==e.id||750!==f[0]||"pageTypeId_7"!==c.id&&"pageTypeId_1"!==c.id||-1!==window.location.href.indexOf("0,0.")?"007-CONTENTBOARD"!==e.id||750===f[0]||"pageTypeId_7"!==c.id&&"pageTypeId_1"!==c.id||adviewDFP.cleanWideContentBoard(e.id):adviewDFP.wideContentBoard(e.id,f),("pageTypeId_7"===c.id||"pageTypeId_1"===c.id)&&"007-CONTENTBOARD"===e.id){750!==document.getElementById("div-gpt-ad-007-CONTENTBOARD-0").clientWidth&&adviewDFP.cleanWideContentBoard(e.id)}}}},100)},adviewDFP.adviewRendered=function(a){var b=a.slot,c=null;Object.keys(b).map(function(a,d){var e=b[a];"string"==typeof e&&e.length&&-1!==e.indexOf("/75224259/")&&(c=e)}),"string"==typeof c&&c.length||console.warn("DFP slot is empty");var d=/[0-9]{3}-(.+)/,e=d.exec(c)[0],f=document.getElementById(e),g=document.getElementById("div-gpt-ad-"+e+"-0"),h=f.getElementsByTagName("span")[0],i=f.getElementsByTagName("iframe")[0],j=document.getElementsByTagName("body")[0];if(void 0!==window.top.dfpRendered&&window.top.dfpRendered({active:a.isEmpty,id:e,height:a.size?a.size[1]:"creative"}),a.isEmpty)return!1;-1===f.className.indexOf("activeBan")&&(f.className+=" activeBan"),f.setAttribute("data-ad-width",a.size[0]),f.setAttribute("data-ad-height",a.size[1]),a.size[0]>10&&(g.style.maxWidth=a.size[0]+"px",h.style.maxWidth=a.size[0]+"px"),h.style.marginLeft="auto",h.style.marginRight="auto",window.dfpParams.adsType&&window.dfpParams.adsType.scrollBanner&&adviewDFP.setClassToBody("scrollBannerDFP"),adviewDFP.setLabel(f),dfpParams.iframeResponsive&&i.getAttribute("responsive-iframe")&&adviewDFP.setIframeResponsive(i),"007-CONTENTBOARD"!==e||750!==a.size[0]||"pageTypeId_7"!==j.id&&"pageTypeId_1"!==j.id||-1!==window.location.href.indexOf("0,0.")?"007-CONTENTBOARD"!==ban.id||750===banSize[0]||"pageTypeId_7"!==j.id&&"pageTypeId_1"!==j.id||adviewDFP.cleanWideContentBoard(e):adviewDFP.wideContentBoard(e,a.size),"071-WINIETA"===e&&-1!==adviewDFP.arrayLabel.indexOf(e)&&adviewDFP.setWinieta()},adviewDFP.setClassToBody=function(a){var b=document.getElementsByTagName("body")[0];-1===b.className.indexOf(a)&&(b.className+=" "+a)},adviewDFP.wideContentBoard=function(a,b){var c=document.getElementById(a),d=document.getElementById("div-gpt-ad-"+a+"-0"),e=c.getElementsByClassName("banLabel")[0];-1===c.className.indexOf("wideContentBoard")&&(c.className+=" wideContentBoard"),c.style.marginBottom="30px",c.style.height=b[1]+e.clientHeight+"px",d.style.position="absolute",d.style.right=0},adviewDFP.cleanWideContentBoard=function(a){var b=document.getElementById(a),c=document.getElementById("div-gpt-ad-"+a+"-0");b.style.cssText="",c.style.cssText="",b.classList.remove("wideContentBoard")},adviewDFP.setWinieta=function(){var a=document.getElementById("pageHead"),b=document.createElement("span");b.className="banLabel winieta-lab",b.appendChild(document.createTextNode("REKLAMA")),a.appendChild(b)},adviewDFP.checkBan=function(){for(var a=document.getElementsByClassName("adviewDFPBanner"),b=0;b<a.length;b++){var c=a[b];c.offsetHeight>40&&adviewDFP.setLabel(c)}},adviewDFP.setLabel=function(a){var b=a,c=b.getElementsByClassName("banLabel")[0];if(c){if(-1!==adviewDFP.arrayLabel.indexOf(b.id)&&b.offsetHeight>40){c.style.display="block";for(var d=b.getElementsByTagName("*"),e=0,f=0;f<d.length;f++){var g=d[f],h=parseInt(g.style.width);h&&h>e?e=h:parseInt(g.width)&&parseInt(g.width)>e&&(e=g.width)}c.style.maxWidth=e+"px"}"undefined"!=typeof abp&&abp&&-1===b.className.indexOf("adblock")&&(b.className+=" adblock"),-1===b.className.indexOf("activeBan")&&b.offsetHeight>40&&(b.className+=" activeBan")}},adviewDFP.firefox=function(){var a=document.getElementsByClassName("banIndexDFP")[0];a&&(a.style.display="block",setTimeout(function(){"0px"===a.getElementsByTagName("iframe")[0].style.height&&a.parentNode.removeChild(a)},500))},adviewDFP.adBlock=function(){for(var a=["div-001-TOPBOARD-ABP","div-003-RECTANGLE-ABP","div-000-MAINBOARD-ABP","div-087-ADBOARD-A-ABP","div-087-ADBOARD-B-ABP","div-111-BIZBUTTON-ABP"],b=0;b<a.length;b++){var c=document.getElementById(a[b]);if(c&&c.clientHeight>40){var d=document.createElement("span");d.className="banLabel",d.appendChild(document.createTextNode("Reklama")),c.insertBefore(d,c.getElementsByTagName("div")[0]),-1===c.className.indexOf("activeBan")&&(c.className+="activeBan")}}},adviewDFP.iframeBan=function(a,b,c,d,e){if(!a)return!1;for(var f=document,g=f.getElementById(a);g.hasChildNodes();)g.removeChild(g.lastChild);var h=f.createElement("iframe"),i=f.createElement("div");i.className="fifContainer fif-container-"+a,h.src=b,h.style.width=d+"px",h.style.height=e+"px",h.style.margin=0,h.style.borderWidth=0,h.style.padding=0,h.scrolling="no",h.frameBorder=0,h.allowTransparency=!0,h.className="banDfpFIF",h.id=a+"_FIF",h.EAS_src=c,h.setAttribute("data-type","fif"),i.appendChild(h),g.appendChild(i)},adviewDFP.checkAgain=function(){setTimeout(function(){adviewDFP.checkBan()},3e3)},adviewDFP.onElementHeightChange=function(a,b){var c,d=a.clientHeight;!function e(){c=a.clientHeight,d!=c&&b(c),d=c,a.onElementHeightChangeTimer&&clearTimeout(a.onElementHeightChangeTimer),a.onElementHeightChangeTimer=setTimeout(e,200)}()},adviewDFP.scrollRect=function(){var a=document.getElementById("col_left");if(!a)return!1;var b=a.clientHeight,c=document.getElementById("col_right"),d=document.getElementById("holder_301"),e=document.getElementsByTagName("body")[0];c.style.minHeight=b+"px",d.style.minHeight=b+"px",document.getElementById("page").style.overflow="initial",-1===e.className.indexOf("scroll035Rectangle")&&(e.className+=" scroll035Rectangle")},adviewDFP.findClosestClass=function(a,b){for(;(a=a.parentElement)&&!a.classList.contains(b););return a},adviewDFP.setIframeResponsive=function(a){var b=a,c=b.getAttribute("data-prop"),d=parseInt(b.getAttribute("width")),e=parseInt(b.contentWindow.document.body.clientWidth);document.getElementsByTagName("body")[0].className,parseInt(adviewDFP.findClosestClass(b,"adviewDFPBanner").getAttribute("data-ad-height"));if(c||(c=parseInt(b.getAttribute("height"))/d,b.setAttribute("data-prop",c)),e!=d){var f=Math.round(e*c);b.style.height=f+"px"}},adviewDFP.getMobileIframe=function(){var a=document.querySelectorAll('iframe[id^="google_ads_iframe"]');if(!a.length&&!dfpParams.iframeResponsive)return!1;for(var b=0;b<a.length;b++){var c=a[b];c.clientHeight>40&&c.getAttribute("responsive-iframe")&&adviewDFP.setIframeResponsive(c)}},adviewDFP.removeDefaultLabel=function(a){var b=adviewDFP.arrayLabel.indexOf(a);-1!==adviewDFP.arrayLabel&&adviewDFP.arrayLabel.splice(b,1)},adviewDFP.isElementInViewport=function(a,b){var c=document.getElementById(a);if(!c)return console.warn('(DFP) ID: "'+a+'" not exists'),!1;var d=c.getBoundingClientRect();return b=b?Number(b):300,d.bottom>0&&d.right>0&&d.left<(window.innerWidth||document.documentElement.clientWidth)&&d.top<(window.innerHeight||document.documentElement.clientHeight)+b},window.onresize=function(){document.getElementsByTagName("body")[0];dfpParams.iframeResponsive&&adviewDFP.getMobileIframe()},adviewDFP.init(),document.addEventListener("DOMContentLoaded",function(){document.getElementsByTagName("body")[0].className+=" dfp-"+dfpParams.dir,adviewDFP.randomLoadMargin(),dfpParams.isMobile||-1!==window.location.href.indexOf("wyborcza.pl")||document.getElementById("content_wrap")&&(adviewDFP.scrollRect(),adviewDFP.onElementHeightChange(document.getElementById("content_wrap"),function(){-1===window.location.href.indexOf("bryla.pl")&&adviewDFP.scrollRect()})),adviewDFP.scrollSlot.forEach(function(a){putBanDFPInViewObject({slot:a,divId:a})},this)}),adviewDFP.randomLoadMargin=function(){var a=[0,100,200,300,400,500,600,700,800,900,1e3];adviewDFP.randomMargin=a[Math.floor(Math.random()*a.length)]},adviewDFP.setAdsForRodo=function(){};var head=document.head||document.getElementsByTagName("head")[0],style=document.createElement("style"),css=".innerContentWrapper button:nth-of-type(2){ display: {%VALUE%} !important; }";css=-1!==window.location.href.indexOf("ugotuj.to")||-1!==window.location.href.indexOf("sport.pl")?css.replace("{%VALUE%}","block"):css.replace("{%VALUE%}","none"),style.type="text/css",style.styleSheet?style.styleSheet.cssText=css:style.appendChild(document.createTextNode(css)),head.appendChild(style); + +	 + +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +				// [HB] Core-prod-4 - HEADER_END +var _sts = new Date(); +var _sleeptime = PREBID_TIMEOUT - (_sts - _st0); +if(_sleeptime > 0){ +	document.write('<scr'+'ipt type="text/javascript" src="https://g.gazeta.pl/przerwa?ttl=' + _sleeptime + '"></scr'+'ipt>'); +} +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 +			 +	 + +	if(lazyGPT) { + +	(function() { +        var useSSL = 'https:' == document.location.protocol; +        var src = (useSSL ? 'https:' : 'http:') + +            '//www.googletagservices.com/tag/js/gpt.js'; +        document.write('<scr' + 'ipt src="' + src + '"></scr' + 'ipt>'); +      })(); +    } + +	</script> + + + + + + +<style type="text/css">.adviewDFPBanner{text-align:center}.banLabel{text-transform:uppercase;margin-top:6px;font:normal 10px Arial,sans-serif;padding-bottom:2px;text-align:left;color:#999}.adviewDFPBanner.activeBan,.adviewDFPBanner.activeBan .banLabel{display:block}.adviewDFPBanner.activeBan{padding:5px 0}.adviewDFPBanner.activeBan>div{margin-left:auto;margin-right:auto}body.screeningLabel-001-TOPBOARD .DFP-001-TOPBOARD{padding-top:5px}body.screeningLabel-001-TOPBOARD .DFP-001-TOPBOARD .banLabel{position:absolute;top:35px;left:10px;max-width:none!important}@media screen and (max-width:1320px){body#pageTypeId_7.screeningLabel-001-TOPBOARD .DFP-001-TOPBOARD{position:relative}body#pageTypeId_7.screeningLabel-001-TOPBOARD .DFP-001-TOPBOARD .banLabel{top:0;left:-53px}}.adviewDFPBanner.activeBan a:hover{background:0 0!important}.DFP-007-CONTENTBOARD{width:100%;position:relative}body.winieta #page-top:not(.fixed){position:relative}body.winieta #page-top:not(.fixed) #pageHead .imgw img{position:absolute;top:0;left:0;z-index:2}body.winieta #page-top:not(.fixed) #pageHead.hasBanner{min-height:90px}#pageHead.hasBanner .banLabel{position:absolute;top:20px;right:-40px;padding:3px 5px;margin:0;transform:rotate(-90deg);background:#fff}body.dfp-forum #pageHead.hasBanner .banLabel{right:-36px}body.dfp-forum.winieta{width:auto!important}#pageHead.hasBanner .column.col1{display:inline-block;position:relative;z-index:2;height:100%}body.dfp-gazetawyborcza #pageHead.hasBanner .c0{top:40px;position:absolute}.DFP-091-RELATED .kd_ns_logo{background:0 0!important;width:auto!important;height:auto!important;margin:auto!important}#div-gpt-ad-091-RELATED-0{max-width:100%!important}body.screeningADFP #page{position:initial}.isScreening #page-top{cursor:pointer}body.screeningADFP.desk .DFP-001-TOPBOARD.activeBan{background:0 0}body.scrollBannerDFP .DFP-001-TOPBOARD .banLabel{position:absolute}#article-list .banner,.indexBanner,div.banIndexDFP,li.banIndexDFP,li.entry.banIndexDFP{display:none}div[id*="-MOBI"].adviewDFPBanner a img{width:100%;height:auto}div[id*="-MOBI"].adviewDFPBanner iframe[responsive-iframe=true]{max-width:100%;width:100%!important;height:auto}body.dfp-mobiHP .banLabel,body.dfp-mobiHP div[id*=div-gpt-ad-]{max-width:100%!important}.dfp-video-bg #page{overflow:initial}.dfp-video-bg .photostoryNextPage,.dfp-video-bg .photostoryPrevPage{z-index:1!important}#div-001-TOPBOARD-ABP .banLabel,#div-003-RECTANGLE-ABP .banLabel{display:block!important}body.scroll035Rectangle .DFP-035-RECTANGLE-BTF{position:sticky!important;position:-webkit-sticky!important;position:-moz-sticky!important;top:40px!important}body.socialContent .DFP-001-TOPBOARD,body.socialContent .DFP-101-TOPBOARD-MOBI{position:relative}body.socialContent .DFP-001-TOPBOARD.activeBan .banLabel,body.socialContent .DFP-101-TOPBOARD-MOBI.activeBan .banLabel{width:100%;position:absolute;left:0;top:0;margin:0;padding:10px;box-sizing:border-box;font-family:Roboto,sans-serif;font-size:8px;letter-spacing:.7px;max-width:none!important;background:-webkit-linear-gradient(rgba(0,0,0,.2),rgba(0,0,0,0));background:-o-linear-gradient(rgba(0,0,0,.2),rgba(0,0,0,0));background:-moz-linear-gradient(rgba(0,0,0,.2),rgba(0,0,0,0));background:linear-gradient(rgba(0,0,0,.2),rgba(0,0,0,0))}body.socialContent .DFP-007-CONTENTBOARD .banLabel,body.socialContent .DFP-104-RECTANGLE-MOBI .banLabel{display:none!important}body.socialContent .DFP-101-TOPBOARD-MOBI>div,body.socialContent .DFP-104-RECTANGLE-MOBI>div{max-width:none!important}body[id^=pageTypeId_0] .adviewDFPBanner.activeBan,body[id^=pageTypeId_56] .adviewDFPBanner.activeBan,body[id^=pageTypeId_7] .adviewDFPBanner.activeBan{margin-bottom:30px;padding-bottom:0}.desk .adviewDFPBanner.DFP-001-TOPBOARD.activeBan{margin-bottom:15px}body.alternative .adviewDFPBanner.activeBan{padding-top:0!important;padding-bottom:20px!important;margin-top:0!important;margin-bottom:0!important}body.alternative .adviewDFPBanner.activeBan .banLabel{padding-top:15px!important;padding-bottom:10px!important;margin-top:0!important;margin-bottom:0!important}body.alternative .DFP-003-RECTANGLE.adviewDFPBanner.activeBan .banLabel{padding-top:0!important}body.alternative .article .adviewDFPBanner.activeBan{margin-bottom:20px!important}body.alternative .DFP-035-RECTANGLE-BTF,body.alternative .DFP-067-RECTANGLE-BTF{position:sticky;top:30px}#banC1.activeBan,#banC2.activeBan,#banC3.activeBan,#banC4.activeBan,#banC9.activeBan{position:relative}#banC1.wideContentBoard>iframe,#banC2.wideContentBoard>iframe,#banC3.wideContentBoard>iframe,#banC4.wideContentBoard>iframe,#banC9.wideContentBoard>iframe{position:absolute;right:0}#article_body #banC1.wideContentBoard iframe,#article_body #banC2.wideContentBoard iframe,#article_body #banC3.wideContentBoard iframe,#article_body #banC4.wideContentBoard iframe,#article_body #banC9.wideContentBoard iframe{max-width:initial}.DFP-201-PREMIUMBOARD-MOBI.activeBan,.DFP-201-PREMIUMBOARD.activeBan{display:none}@media screen and (orientation:portrait){div.oaoa__interstitial{height:100%!important}}@media all and (max-width:330px){.responsive .adviewDFPBanner.activeBan{padding-left:0!important;padding-right:0!important}}.DFP-019-TOPLAYER{margin:0!important}body.path_sport-hp .DFP-087-ADBOARD-A{position:relative;z-index:9}body.path_sport-hp .DFP-087-ADBOARD-A .banLabel{background:linear-gradient(rgba(255,255,255,0),#fff)}body.path_sport-hp .DFP-087-ADBOARD-A:before{content:"";position:absolute;bottom:-7px;left:0;width:100%;height:13px;background:linear-gradient(#fff,rgba(255,255,255,0))}.desk.screeningADFP.none #content #content_wrap{margin-top:0!important}body.screeningADFP.dfp-czterykaty #bottom_wrapper,body.screeningADFP.dfp-czterykaty #main_wrapper,body.screeningADFP.dfp-czterykaty #top_wrapper,body.screeningADFP.dfp-czterykaty .aside_wrapper{max-width:1242px;margin:0 auto}.fifContainer{font-size:.1px;line-height:0}.DFP-113-LOKALBUTTON{overflow:visible!important}</style> +<!-- v2.6 --> +
 +
 +<!-- v2.8 adapter biznes /biznes-->
 + +<!--10185226, [ /tpl/ads/prod/dfpHeader.jsp ], dfpBanersHeaderBean--> +
 +	
 +
 +
 +
 +<!-- facebookConnect v1.41 -->
 +
 +
 +
 +
 +
 +
 +<!--[if IE]><script>(function(t) { for (var i = 0, l = t.length; i < l; i++) document.createElement(t[i]); })(['fb','og']);</script><![endif]-->
 +
 +	
 +	
 +		<div id="fb-root"></div>
 +		<script>
 +		window.gazeta_pl = window.gazeta_pl || {};
 +		window.gazeta_pl.functionQueue = window.gazeta_pl.functionQueue || [];
 +		window.gazeta_pl.functionQueue.push(function(){
 +			setTimeout(function() {
 +                (function(d){
 +                   var js, id = 'facebook-jssdk'; if (d.getElementById(id)) {return;}
 +                   js = d.createElement('script'); js.id = id; js.async = true;
 +                   js.src = "//connect.facebook.net/pl_PL/all.js";
 +                   d.getElementsByTagName('head')[0].appendChild(js);
 +                 }(document));
 +			}, 2000);
 +			function facebookReady(){
 +				FB.init({
 +			      appId  : 515714931781741,
 +			      status : true,
 +			      cookie : true,
 +			      xfbml  : true,
 +			      oauth  : true,
 +			      version: 'v2.2'
 +			    });
 +			}
 +			if(window.FB) {
 +				facebookReady();
 +			} else {
 +				window.fbAsyncInit = facebookReady;
 +			}
 +		});
 +		</script>
 +	
 +
 + +<!--9638937, [ /fix/modules/facebook/facebookConnect.jsp ], null--> +
 +</head>
 +
 +
 +
 +
 +
 +    
 +    
 +    
 +        
 +        
 +    
 +
 +
 +
 +
 +
 +<body id="pagetype_art" class=" awd    path_147743 path_biznes simpleArt  unknown Other unknown LINUX  ">
 +
 +	<!-- pattern#ms - Article 7 -  app18:tomcat-wyborcza: -  - 3200118 - s3200007_P20_wyb_2017_desk.jsp -  v1.0.1 -->
 +
 +	
 +
 +<!-- WyborczaSVG version: 17.0.28 compiled: 2018-08-28 10:52:39 --> <svg id="svgicons" style="display:block;width:0;height:0;"><symbol id="admin-censore" viewBox="0 0 19.5 19.5"><path d="M438,444.25a9.75,9.75,0,1,1,9.75-9.75A9.76,9.76,0,0,1,438,444.25Zm0-17a7.25,7.25,0,1,0,7.25,7.25A7.26,7.26,0,0,0,438,427.25Z" transform="translate(-428.25 -424.75)"/><path d="M430.52,436.84L443.74,429l1.59,2.71-13.22,7.81Z" transform="translate(-428.25 -424.75)"/></symbol><symbol id="admin-clear" viewBox="0 0 19.63 16.99"><path d="M446.86,418.74v-2h-2.61l-6.12-6.22-4.76,4.71h0l-1.41,1.41h0l-4.71,4.66,6.12,6.22,4.76-4.71h0l1.41-1.41h0l2.68-2.65h4.64Zm-13.48,5.51-2.9-2.94,3.09-3.06,2.92,2.92Zm4.53-4.48L435,416.85l3.11-3.08,2.9,2.94Z" transform="translate(-427.24 -410.51)"/></symbol><symbol id="admin-delete" viewBox="0 0 166.86 170"><polygon points="73.5 170 0 170 0 31.87 30.6 0 141 0 141 86.28 118 86.28 118 23 40.4 23 23 41.13 23 147 73.5 147 73.5 170"/><rect x="33.5" y="40.5" width="75" height="13"/><rect x="33.5" y="65.5" width="75" height="13"/><polygon points="166.86 158.06 140.19 132.39 166.86 104.71 155.67 93.53 129 120.2 102.33 93.53 91.14 104.71 117.81 131.39 91.14 158.06 102.33 169.25 129 142.57 155.67 169.25 166.86 158.06"/></symbol><symbol id="admin-expose" viewBox="0 0 36.54 34.75"><polygon points="18.27 0 23.92 11.44 36.54 13.27 27.41 22.18 29.56 34.75 18.27 28.81 6.98 34.75 9.13 22.18 0 13.27 12.62 11.44 18.27 0"/></symbol><symbol id="admin-publish" viewBox="0 0 19.5 19.5"><polygon points="14.44 8.91 10.59 8.91 10.59 5.06 8.91 5.06 8.91 8.91 5.06 8.91 5.06 10.59 8.91 10.59 8.91 14.44 10.59 14.44 10.59 10.59 14.44 10.59 14.44 8.91"/><path d="M423,489.33a9.75,9.75,0,1,1,9.75-9.75A9.75,9.75,0,0,1,423,489.33Zm0-17a7.25,7.25,0,1,0,7.25,7.25h0A7.26,7.26,0,0,0,423,472.33Z" transform="translate(-413.25 -469.83)"/></symbol><symbol id="admin-undo-expose" viewBox="0 0 16 13.99"><polygon points="16 2.54 6.47 2.54 7.38 1.5 6.08 0 0 7 1.3 8.49 1.3 8.49 6.08 13.99 7.38 12.49 6.47 11.45 16 11.45 16 2.54"/></symbol><symbol id="app-store" viewBox="0 0 135 40"><g><path class="store-frame" d="m130.2 40h-125.47c-2.608 0-4.73-2.128-4.73-4.733v-30.541c0-2.606 2.122-4.726 4.729-4.726h125.47c2.6 0 4.8 2.12 4.8 4.726v30.541c0 2.605-2.2 4.733-4.8 4.733z" fill="#A6A6A6"/><path d="m134.03 35.268c0 2.116-1.714 3.83-3.834 3.83h-125.47c-2.119 0-3.839-1.714-3.839-3.83v-30.543c0-2.115 1.72-3.835 3.839-3.835h125.47c2.121 0 3.834 1.72 3.834 3.835v30.543z" class="store-bkg"/></g><g fill="#fff"><path d="m30.128 19.795c-0.029-3.223 2.639-4.791 2.761-4.864-1.511-2.203-3.853-2.504-4.676-2.528-1.967-0.207-3.875 1.177-4.877 1.177-1.022 0-2.565-1.157-4.228-1.123-2.14 0.033-4.142 1.272-5.24 3.196-2.266 3.923-0.576 9.688 1.595 12.858 1.086 1.553 2.355 3.287 4.016 3.226 1.625-0.067 2.232-1.036 4.193-1.036 1.943 0 2.513 1.036 4.207 0.997 1.744-0.028 2.842-1.56 3.89-3.127 1.255-1.78 1.759-3.533 1.779-3.623-0.04-0.013-3.386-1.29-3.42-5.153z"/><path d="m26.928 10.317c0.874-1.093 1.472-2.58 1.306-4.089-1.265 0.056-2.847 0.875-3.758 1.944-0.806 0.942-1.526 2.486-1.34 3.938 1.421 0.106 2.88-0.716 3.792-1.793z"/></g><g fill="#fff"><path d="m53.646 31.516h-2.271l-1.244-3.91h-4.324l-1.185 3.91h-2.211l4.284-13.308h2.646l4.305 13.308zm-3.89-5.549l-1.125-3.475c-0.119-0.355-0.342-1.191-0.671-2.508h-0.04c-0.131 0.566-0.342 1.402-0.632 2.508l-1.105 3.475h3.573z"/><path d="m64.663 26.599c0 1.632-0.441 2.923-1.323 3.87-0.79 0.842-1.771 1.264-2.942 1.264-1.264 0-2.172-0.455-2.725-1.363h-0.04v5.055h-2.132v-10.347c0-1.026-0.027-2.079-0.079-3.158h1.875l0.119 1.52h0.04c0.711-1.145 1.79-1.717 3.238-1.717 1.132 0 2.077 0.447 2.833 1.342 0.757 0.895 1.136 2.073 1.136 3.534zm-2.172 0.079c0-0.935-0.21-1.705-0.632-2.311-0.461-0.631-1.08-0.947-1.856-0.947-0.526 0-1.004 0.175-1.431 0.523-0.428 0.349-0.708 0.807-0.839 1.372-0.066 0.264-0.099 0.48-0.099 0.651v1.6c0 0.697 0.214 1.287 0.642 1.768s0.984 0.721 1.668 0.721c0.803 0 1.428-0.311 1.875-0.928 0.448-0.619 0.672-1.436 0.672-2.449z"/><path d="m75.7 26.599c0 1.632-0.441 2.923-1.324 3.87-0.789 0.842-1.77 1.264-2.941 1.264-1.264 0-2.172-0.455-2.725-1.363h-0.039v5.055h-2.132v-10.347c0-1.026-0.027-2.079-0.079-3.158h1.875l0.119 1.52h0.04c0.711-1.145 1.789-1.717 3.238-1.717 1.131 0 2.076 0.447 2.834 1.342 0.755 0.895 1.134 2.073 1.134 3.534zm-2.172 0.079c0-0.935-0.211-1.705-0.633-2.311-0.461-0.631-1.078-0.947-1.855-0.947-0.527 0-1.004 0.175-1.432 0.523s-0.707 0.807-0.839 1.372c-0.065 0.264-0.099 0.48-0.099 0.651v1.6c0 0.697 0.214 1.287 0.641 1.768 0.428 0.48 0.984 0.721 1.67 0.721 0.803 0 1.428-0.311 1.875-0.928 0.448-0.619 0.672-1.436 0.672-2.449z"/><path d="m88.04 27.783c0 1.133-0.394 2.053-1.182 2.764-0.867 0.777-2.075 1.166-3.625 1.166-1.432 0-2.581-0.277-3.449-0.83l0.494-1.777c0.935 0.566 1.962 0.85 3.081 0.85 0.804 0 1.429-0.182 1.877-0.543 0.447-0.363 0.671-0.848 0.671-1.455 0-0.539-0.185-0.994-0.553-1.363s-0.98-0.713-1.836-1.029c-2.33-0.869-3.495-2.143-3.495-3.816 0-1.094 0.408-1.991 1.225-2.69 0.815-0.699 1.901-1.048 3.258-1.048 1.211 0 2.218 0.211 3.021 0.632l-0.533 1.738c-0.75-0.408-1.599-0.612-2.547-0.612-0.75 0-1.336 0.185-1.757 0.553-0.355 0.329-0.533 0.73-0.533 1.204 0 0.526 0.204 0.961 0.612 1.304 0.355 0.315 1 0.658 1.935 1.026 1.146 0.461 1.987 1.001 2.527 1.618s0.809 1.387 0.809 2.308z"/><path d="m95.088 23.519h-2.35v4.659c0 1.186 0.415 1.777 1.244 1.777 0.382 0 0.697-0.033 0.948-0.098l0.059 1.619c-0.421 0.156-0.974 0.236-1.658 0.236-0.843 0-1.501-0.258-1.975-0.77-0.474-0.514-0.711-1.377-0.711-2.588v-4.837h-1.401v-1.597h1.401v-1.758l2.093-0.632v2.39h2.35v1.599z"/><path d="m105.69 26.639c0 1.475-0.422 2.686-1.264 3.633-0.882 0.975-2.054 1.461-3.515 1.461-1.409 0-2.53-0.467-3.366-1.402-0.836-0.934-1.254-2.113-1.254-3.533 0-1.488 0.431-2.705 1.293-3.653s2.024-1.421 3.485-1.421c1.408 0 2.54 0.467 3.396 1.401 0.8 0.907 1.21 2.078 1.21 3.514zm-2.21 0.068c0-0.885-0.19-1.643-0.572-2.277-0.447-0.766-1.086-1.148-1.915-1.148-0.856 0-1.508 0.383-1.955 1.148-0.382 0.635-0.572 1.406-0.572 2.317 0 0.885 0.19 1.644 0.572 2.276 0.461 0.766 1.105 1.148 1.936 1.148 0.815 0 1.454-0.389 1.915-1.168 0.38-0.646 0.58-1.411 0.58-2.296z"/><path d="m112.62 23.795c-0.211-0.039-0.435-0.059-0.671-0.059-0.751 0-1.33 0.283-1.738 0.849-0.355 0.501-0.533 1.132-0.533 1.896v5.035h-2.132l0.02-6.575c0-1.106-0.026-2.112-0.079-3.021h1.856l0.079 1.836h0.059c0.224-0.632 0.579-1.139 1.066-1.521 0.474-0.343 0.987-0.513 1.54-0.513 0.197 0 0.375 0.013 0.533 0.039v2.034z"/><path d="m122.16 26.264c0 0.381-0.026 0.703-0.079 0.967h-6.396c0.025 0.947 0.335 1.672 0.928 2.172 0.539 0.447 1.237 0.672 2.093 0.672 0.947 0 1.81-0.152 2.587-0.455l0.335 1.48c-0.908 0.396-1.981 0.594-3.218 0.594-1.488 0-2.656-0.438-3.505-1.314-0.849-0.875-1.273-2.049-1.273-3.523 0-1.447 0.395-2.652 1.185-3.613 0.829-1.027 1.948-1.54 3.356-1.54 1.382 0 2.429 0.513 3.14 1.54 0.55 0.815 0.84 1.821 0.84 3.02zm-2.04-0.553c0.013-0.633-0.126-1.179-0.415-1.64-0.368-0.593-0.935-0.888-1.698-0.888-0.697 0-1.264 0.289-1.697 0.868-0.355 0.461-0.566 1.014-0.632 1.659h4.45z"/></g><g fill="#fff"><path d="m47.867 8.808c0 0.602-0.178 1.083-0.533 1.445-0.459 0.472-1.129 0.708-2.008 0.708-0.259 0-0.46-0.016-0.602-0.048v2.532h-1.048v-6.451c0.499-0.09 1.069-0.136 1.711-0.136 0.83 0 1.455 0.178 1.877 0.534 0.402 0.349 0.603 0.821 0.603 1.416zm-1.048 0.048c0-0.382-0.122-0.674-0.366-0.878-0.245-0.204-0.586-0.306-1.023-0.306-0.29 0-0.525 0.02-0.705 0.058v2.348c0.147 0.039 0.351 0.058 0.608 0.058 0.463 0 0.827-0.113 1.091-0.339s0.395-0.54 0.395-0.941z"/><path d="m53.727 11.048c0 0.725-0.207 1.319-0.621 1.785-0.434 0.479-1.009 0.718-1.727 0.718-0.692 0-1.243-0.229-1.654-0.689-0.41-0.459-0.615-1.038-0.615-1.736 0-0.73 0.211-1.329 0.635-1.794s0.994-0.698 1.712-0.698c0.692 0 1.248 0.229 1.669 0.688 0.399 0.446 0.601 1.022 0.601 1.726zm-1.087 0.035c0-0.435-0.094-0.808-0.281-1.119-0.22-0.376-0.533-0.564-0.94-0.564-0.421 0-0.741 0.188-0.961 0.564-0.188 0.311-0.281 0.69-0.281 1.138 0 0.435 0.094 0.808 0.281 1.119 0.227 0.376 0.543 0.564 0.951 0.564 0.4 0 0.714-0.191 0.94-0.574 0.194-0.318 0.291-0.694 0.291-1.128z"/><path d="m59.769 11.02c0 0.795-0.22 1.429-0.659 1.901-0.388 0.42-0.863 0.631-1.426 0.631-0.673 0-1.168-0.278-1.484-0.834h-0.02l-0.058 0.728h-0.893c0.025-0.381 0.038-0.805 0.038-1.271v-5.608h1.048v2.852h0.02c0.311-0.524 0.812-0.786 1.504-0.786 0.568 0 1.032 0.218 1.392 0.655 0.358 0.437 0.538 1.014 0.538 1.732zm-1.067 0.038c0-0.459-0.104-0.834-0.311-1.125-0.227-0.317-0.534-0.476-0.922-0.476-0.259 0-0.491 0.084-0.698 0.252s-0.346 0.391-0.417 0.669c-0.026 0.11-0.039 0.22-0.039 0.33v0.824c0 0.324 0.108 0.602 0.325 0.834s0.486 0.349 0.81 0.349c0.395 0 0.702-0.148 0.922-0.446 0.22-0.296 0.33-0.7 0.33-1.211z"/><path d="m62.485 7.324c0 0.188-0.062 0.339-0.184 0.456-0.123 0.117-0.281 0.175-0.476 0.175-0.175 0-0.322-0.06-0.441-0.179-0.12-0.12-0.18-0.27-0.18-0.451s0.062-0.33 0.185-0.446 0.274-0.175 0.456-0.175c0.181 0 0.333 0.059 0.456 0.175 0.123 0.115 0.184 0.264 0.184 0.445zm-0.116 6.12h-1.048v-4.714h1.048v4.714z"/><path d="m68.111 10.864c0 0.188-0.014 0.346-0.039 0.475h-3.142c0.013 0.466 0.164 0.821 0.455 1.067 0.266 0.22 0.608 0.33 1.028 0.33 0.466 0 0.89-0.074 1.271-0.223l0.164 0.728c-0.446 0.194-0.973 0.291-1.581 0.291-0.73 0-1.305-0.215-1.722-0.645s-0.625-1.007-0.625-1.731c0-0.711 0.193-1.303 0.582-1.775 0.407-0.504 0.956-0.756 1.648-0.756 0.679 0 1.193 0.252 1.542 0.756 0.28 0.4 0.419 0.895 0.419 1.483zm-1-0.271c0.007-0.311-0.062-0.579-0.203-0.805-0.182-0.291-0.459-0.437-0.834-0.437-0.343 0-0.621 0.142-0.835 0.427-0.174 0.227-0.277 0.498-0.31 0.815h2.182z"/><path d="m72.126 9.652c-0.104-0.02-0.213-0.029-0.33-0.029-0.368 0-0.652 0.139-0.854 0.417-0.174 0.246-0.262 0.556-0.262 0.931v2.474h-1.048l0.01-3.23c0-0.543-0.013-1.038-0.038-1.484h0.911l0.039 0.902h0.029c0.109-0.311 0.284-0.56 0.523-0.747 0.233-0.168 0.485-0.252 0.757-0.252 0.097 0 0.185 0.006 0.262 0.019v0.999z"/><path d="m76.83 13.444h-3.841v-0.611l1.882-2.473c0.116-0.155 0.329-0.411 0.64-0.767v-0.019h-2.338v-0.844h3.608v0.65l-1.843 2.435c-0.207 0.265-0.42 0.521-0.64 0.766v0.02h2.531v0.843z"/><path d="m87.365 8.73l-1.475 4.714h-0.96l-0.611-2.047c-0.155-0.511-0.281-1.019-0.379-1.523h-0.019c-0.091 0.518-0.217 1.025-0.379 1.523l-0.649 2.047h-0.971l-1.386-4.714h1.077l0.533 2.241c0.129 0.53 0.235 1.035 0.32 1.513h0.019c0.078-0.394 0.207-0.896 0.389-1.503l0.669-2.25h0.854l0.641 2.202c0.155 0.537 0.281 1.054 0.378 1.552h0.029c0.071-0.485 0.178-1.002 0.32-1.552l0.572-2.202h1.028z"/></g></symbol><symbol id="arrow-back" viewBox="0 0 17 9"><path d="M0,4.5L5.7,0v2.2C15.9,2.2,17,7.9,17,9C14.7,4.5,6.8,6.8,5.7,6.8V9L0,4.5z"/></symbol><symbol id="bell" viewBox="0 0 32 32"><path d="M15.7,29.5c-2.8,0-4-1.9-4.2-2.9c-0.1-0.2,0.1-0.5,0.3-0.5c0.2-0.1,0.5,0.1,0.5,0.3
 +        c0.2,0.8,1.1,2.2,3.4,2.2c2.3,0,3.2-1.4,3.4-2.2c0.1-0.2,0.3-0.4,0.5-0.3c0.2,0.1,0.4,0.3,0.3,0.5C19.7,27.6,18.5,29.5,15.7,29.5z"/><path d="M26.2,22.8c-1.2-1-3.7-3.1-3.7-7.7c0-4.2-1.6-6.5-2.9-7.6c-1.2-1.1-2.6-1.5-3.4-1.6V3.8
 +        c0-0.2-0.2-0.4-0.4-0.4c-0.2,0-0.4,0.2-0.4,0.4v2.1c-0.8,0.1-2.2,0.5-3.4,1.6C10.5,8.7,9,10.9,9,15.1c0,4.5-2.5,6.6-3.7,7.7
 +        c-0.5,0.5-0.9,0.7-0.7,1.1c0.2,0.5,0.7,0.5,2.4,0.5h17.7c1.7,0,2.2,0,2.4-0.5C27,23.5,26.7,23.3,26.2,22.8z M24.5,23.6H6.8
 +        c-0.3,0-0.8,0-1.2,0c0,0,0.1-0.1,0.1-0.1c1.2-1,4.1-3.4,4.1-8.3c0-3.1,0.9-5.5,2.6-7c1.4-1.2,2.8-1.4,3.3-1.4
 +        c0.5,0,1.9,0.3,3.3,1.4c1.7,1.5,2.6,3.8,2.6,7c0,4.9,2.8,7.3,4.1,8.3c0,0,0.1,0.1,0.1,0.1C25.3,23.6,24.8,23.6,24.5,23.6z"/></symbol><symbol id="blog" viewBox="0 0 38.2 39.4"><path d="m 30.9,37.8 -23.5,0 -4.3,1.6 27.8,0 c 0.4,0 0.8,-0.3 0.8,-0.8 0,-0.5 -0.3,-0.8 -0.8,-0.8 z"/><path d="M 38.2,5.6 32.8,0 28.9,3.9 25.3,0.3 18.1,7.6 19.2,8.7 25.4,2.4 27.8,5 3.1,29.7 c 0,0 0,0 0,0 0,0 0,0 0,0 L 2.9,29.9 2.8,30 0,38.3 8.2,35.6 38.2,5.6 Z M 32.8,2.1 36.2,5.5 33.4,8.3 29.9,5 32.8,2.1 Z M 2.4,35.9 3.8,31.6 6.7,34.5 2.4,35.9 Z M 4.6,30.3 28.9,6 32.3,9.4 8,33.6 4.6,30.3 Z"/></symbol><symbol id="burger" viewBox="-338 41.4 32 32"><rect x="-330.4" y="61.7" width="16.8" height="1.5"/><rect x="-330.4" y="56.6" width="16.8" height="1.5"/><rect x="-330.4" y="51.6" width="16.8" height="1.5"/></symbol><symbol viewBox="0 0 19.5 30" id="chevron"><path d="M 15,0 0,15 15,30 19.5,25.5 9,15 19.5,4.5 Z M 15,1 18.5,4.5 8,15 18.5,25.5 15,29 1,15 Z"/></symbol><symbol id="clock" viewBox="0 0 15 15"><path d="M7.5,0A7.5,7.5,0,1,0,15,7.5,7.5,7.5,0,0,0,7.5,0Zm4.91,8H7V7.5H7V2.59H8V7h4.41Z"/></symbol><symbol id="close-symbol" viewBox="0 0 18 18"><path d="M 0.7,18 0,17.2 17.3,0 18,0.8 0.7,18 z M 0,0.7 0.8,0 18,17.3 17.2,18 0,0.7 z"/></symbol><symbol id="comment" viewBox="0 0 20 20"><path d="M16.7,13.6c1.4,0,2.5-1.1,2.5-2.5V3.5c0-1.3-1.2-2.5-2.5-2.5H3.8C2.3,0.9,1.3,2,1.3,3.5V11 			c0,1.3,1.2,2.5,2.5,2.5h1.7V19l4.8-5.4C10.2,13.6,16.7,13.6,16.7,13.6z"/></symbol><symbol id="commentary" viewBox="0 0 17.9 18"><path d="m 15.4,12.606309 c 1.4,0 2.5,-1.1 2.5,-2.5 V 2.5063091 c 0,-1.3 -1.2,-2.49999999 -2.5,-2.49999999 H 2.5 C 1,-0.09369089 0,1.0063091 0,2.5063091 v 7.4999999 c 0,1.3 1.2,2.5 2.5,2.5 h 1.7 v 5.5 l 4.8,-5.4 c -0.1,0 6.4,0 6.4,0 z"/></symbol><symbol id="comments-v2" viewBox="0 0 34 34"><path d="M11,29v-6H5V8h24v15H17L11,29z"/><path d="M13.3,14.7c0.4,0,0.7,0.3,0.7,0.7s-0.3,0.7-0.7,0.7s-0.7-0.3-0.7-0.7S12.9,14.7,13.3,14.7z"/><path d="M17,14.7c0.4,0,0.7,0.3,0.7,0.7s-0.3,0.7-0.7,0.7s-0.7-0.3-0.7-0.7S16.6,14.7,17,14.7z"/><path d="M20.7,14.7c0.4,0,0.7,0.3,0.7,0.7s-0.3,0.7-0.7,0.7S20,15.8,20,15.4S20.3,14.7,20.7,14.7z"/></symbol><symbol id="diamond" viewBox="0 0 32 32"><path d="M16,29.2L0.9,14v-1.2l5.7-6.5h18.9l5.7,6.5V14L16,29.2z M1.9,13.6L16,27.8l14.1-14.1v-0.4L25,7.4H7l-5.1,5.8V13.6z"/><rect x="1.4" y="12.6" width="29.3" height="1"/><polygon points="16.5,28.7 15.5,28.3 22.4,13.1 19.4,7.1 20.3,6.7 23.5,13 "/><polygon points="15.5,28.7 8.5,13 11.7,6.7 12.6,7.1 9.6,13.1 16.5,28.3 "/></symbol><symbol id="dots" viewBox="0 0 27 7"><path d="M 3.5,0 C 1.6,0 0,1.6 0,3.5 0,5.4 1.6,7 3.5,7 5.4,7 7,5.4 7,3.5 7,1.6 5.4,0 3.5,0 z m 10,0 C 11.6,0 10,1.6 10,3.5 10,5.4 11.6,7 13.5,7 15.4,7 17,5.4 17,3.5 17,1.6 15.4,0 13.5,0 z m 10,0 C 21.6,0 20,1.6 20,3.5 20,5.4 21.6,7 23.5,7 25.4,7 27,5.4 27,3.5 27,1.6 25.4,0 23.5,0 z"/></symbol><symbol id="down-arrow" viewBox="0 0 21.2 14.1"><rect x="6.7" y="4.6" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -0.8498 12.1094)" width="15" height="5"/><rect x="4.6" y="-0.4" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -2.917 7.1)" width="5" height="15"/></symbol><symbol id="envelope" viewBox="0 0 50 32"><path d="M 48.1,-2.4512111e-7 H 2.1 1.7 0.7 0 V 32 H 50 V -2.4512111e-7 H 48.3 48.1 z M 46.1,1.9999998 26.7,21.3 c -0.9,0.9 -2.4,0.9 -3.3,0 L 4.1,1.9999998 h 42 z m -44,0.8 L 15.1,15.8 2.1,28.8 V 2.7999998 z M 3.7,30 16.5,17.2 22,22.7 c 0.8,0.8 1.9,1.3 3.1,1.3 1.2,0 2.2,-0.5 3.1,-1.3 L 33.7,17.2 46.5,30 H 3.7 z m 44.4,-1.2 -13,-13 13,-13.0000002 V 28.8 z"/></symbol><symbol id="eye" viewBox="0 0 32 20"><path d="M 16,0 C 7.2,0 0,8 0,10 0,11.8 7.2,20 16,20 24.8,20 32,12 32,10 32,8 24.8,0 16,0 z m 0,16 c -3.3,0 -6,-2.7 -6,-6 0,-3.3 2.2,-6 7,-6 l -1,6 6,-2 c 0,5.6 -2.7,8 -6,8 z"/></symbol><symbol id="face-v2" viewBox="0 0 34 34"><path d="M19,31h-5V19h-3v-4h3V8c0-1.8,0.6-2.9,1.4-3.7C16.8,3,19.5,3,19.9,3L23,3v5h-2c-1.1,0-2,0.4-2,2v5h4v4h-4V31z M22,7"/></symbol><symbol id="face" viewBox="0 0 13.4 29.4"><path d="M 8.1,10.508657 V 6.0086565 c 0,0 0,-1.8 1.9,-1.8 h 2.8 V 0.00865651 H 9.3 c 0,0 -5.9,-0.5 -5.9,6.09999999 0,1.4 0,4.5000005 0,4.5000005 H 0 v 3.4 h 3.4 v 15.4 h 4.7 v -15.4 h 4.4 l 0.9,-3.5 H 8.1 z"/></symbol><symbol id="facebook-logo" viewBox="0 0 28 28"><path fill="inherit" d="m 18.339322,13.572791 h -3.27 v 9.42 h -3.52 v -9.43 H 9.0493224 v -2.13 H 11.559322 V 8.7027914 c 0,-4 4.42,-3.69 4.42,-3.69 h 2.6 v 2.57 h -2.1 a 1.17,1.17 0 0 0 -1.41,1.06 v 2.7899996 h 4 z"></path></symbol><symbol id="gallery" viewBox="0 0 20 15"><path d="M 18.1,1.7999998 H 13.9 L 13.5,0.89999981 C 13.2,0.39999981 12.6,-1.9073486e-7 12.1,-1.9073486e-7 H 7.7 C 7.2,-1.9073486e-7 6.6,0.29999981 6.4,0.89999981 L 6,1.7999998 H 1.8 c -1.2,0 -1.8,0.8 -1.8,1.9 V 13.3 c 0,1 0.6,1.8 1.8,1.8 h 16.4 c 1.1,0 1.8,-0.8 1.8,-1.8 V 3.5999998 c -0.1,-1.2 -0.8,-1.8 -1.9,-1.8 z M 10,13.2 c -3,0 -5.5,-2.4 -5.5,-5.3000002 0.1,-2.9 2.5,-5.3 5.5,-5.3 3,0 5.5,2.4 5.5,5.3 C 15.4,10.9 12.9,13.2 10,13.2 z m 0,-8.8000002 c -2,0 -3.7,1.6 -3.7,3.6 0,2 1.7,3.6000002 3.7,3.6000002 2,0 3.7,-1.6000002 3.7,-3.6000002 0,-2 -1.7,-3.6 -3.7,-3.6 z"/></symbol><symbol id="google-play" viewBox="0 0 135 40"><g><path d="m130 40h-125c-2.8 0-5-2.2-5-5v-30c0-2.7 2.2-5 5-5h125c2.8 0 5 2.2 5 5v30c0 2.7-2.3 5-5 5z" class="store-bkg"/><path class="store-frame" d="m130 0.8c2.3 0 4.2 1.9 4.2 4.2v30c0 2.3-1.9 4.2-4.2 4.2h-125c-2.3 0-4.2-1.9-4.2-4.2v-30c0-2.3 1.9-4.2 4.2-4.2h125m0-0.8h-125c-2.8 0-5 2.3-5 5v30c0 2.8 2.2 5 5 5h125c2.8 0 5-2.2 5-5v-30c0-2.7-2.3-5-5-5z" fill="#A6A6A6"/></g><g><path d="m68.1 21.8c-2.4 0-4.3 1.8-4.3 4.3 0 2.4 1.9 4.3 4.3 4.3s4.3-1.8 4.3-4.3c0-2.6-2-4.3-4.3-4.3zm0 6.8c-1.3 0-2.4-1.1-2.4-2.6s1.1-2.6 2.4-2.6 2.4 1 2.4 2.6c0 1.5-1.1 2.6-2.4 2.6zm-9.3-6.8c-2.4 0-4.3 1.8-4.3 4.3 0 2.4 1.9 4.3 4.3 4.3s4.3-1.8 4.3-4.3c0-2.6-2-4.3-4.3-4.3zm0 6.8c-1.3 0-2.4-1.1-2.4-2.6s1.1-2.6 2.4-2.6 2.4 1 2.4 2.6c0 1.5-1.1 2.6-2.4 2.6zm-11.1-5.5v1.8h4.3c-0.1 1-0.5 1.8-1 2.3-0.6 0.6-1.6 1.3-3.3 1.3-2.7 0-4.7-2.1-4.7-4.8s2.1-4.8 4.7-4.8c1.4 0 2.5 0.6 3.3 1.3l1.3-1.3c-1.1-1-2.5-1.8-4.5-1.8-3.6 0-6.7 3-6.7 6.6s3.1 6.6 6.7 6.6c2 0 3.4-0.6 4.6-1.9 1.2-1.2 1.6-2.9 1.6-4.2 0-0.4 0-0.8-0.1-1.1h-6.2zm45.3 1.4c-0.4-1-1.4-2.7-3.6-2.7s-4 1.7-4 4.3c0 2.4 1.8 4.3 4.2 4.3 1.9 0 3.1-1.2 3.5-1.9l-1.4-1c-0.5 0.7-1.1 1.2-2.1 1.2s-1.6-0.4-2.1-1.3l5.7-2.4-0.2-0.5zm-5.8 1.4c0-1.6 1.3-2.5 2.2-2.5 0.7 0 1.4 0.4 1.6 0.9l-3.8 1.6zm-4.6 4.1h1.9v-12.5h-1.9v12.5zm-3.1-7.3c-0.5-0.5-1.3-1-2.3-1-2.1 0-4.1 1.9-4.1 4.3s1.9 4.2 4.1 4.2c1 0 1.8-0.5 2.2-1h0.1v0.6c0 1.6-0.9 2.5-2.3 2.5-1.1 0-1.9-0.8-2.1-1.5l-1.6 0.7c0.5 1.1 1.7 2.5 3.8 2.5 2.2 0 4-1.3 4-4.4v-7.6h-1.8v0.7zm-2.1 5.9c-1.3 0-2.4-1.1-2.4-2.6s1.1-2.6 2.4-2.6 2.3 1.1 2.3 2.6-1 2.6-2.3 2.6zm24.4-11.1h-4.5v12.5h1.9v-4.7h2.6c2.1 0 4.1-1.5 4.1-3.9s-2.1-3.9-4.1-3.9zm0 6h-2.7v-4.3h2.7c1.4 0 2.2 1.2 2.2 2.1 0 1.1-0.8 2.2-2.2 2.2zm11.5-1.8c-1.4 0-2.8 0.6-3.3 1.9l1.7 0.7c0.4-0.7 1-0.9 1.7-0.9 1 0 1.9 0.6 2 1.6v0.1c-0.3-0.2-1.1-0.5-1.9-0.5-1.8 0-3.6 1-3.6 2.8 0 1.7 1.5 2.8 3.1 2.8 1.3 0 1.9-0.6 2.4-1.2h0.1v1h1.8v-4.8c-0.2-2.2-1.8-3.5-4-3.5zm-0.2 6.9c-0.6 0-1.5-0.3-1.5-1.1 0-1 1.1-1.3 2-1.3 0.8 0 1.2 0.2 1.7 0.4-0.1 1.2-1.1 2-2.2 2zm10.6-6.6l-2.1 5.4h-0.1l-2.2-5.4h-2l3.3 7.6-1.9 4.2h1.9l5.1-11.8h-2zm-16.8 8h1.9v-12.5h-1.9v12.5z" fill="#fff"/></g><g><path d="m10.4 7.5c-0.3 0.3-0.5 0.8-0.5 1.4v22.1c0 0.6 0.2 1.1 0.5 1.4l0.1 0.1 12.4-12.4v-0.2l-12.5-12.4z" fill="#00d3ff"/><path d="m27 24.3l-4.1-4.1v-0.3l4.1-4.1 0.1 0.1 4.9 2.8c1.4 0.8 1.4 2.1 0 2.9l-5 2.7z" fill="#ffb500"/><path d="m27.1 24.2l-4.2-4.2-12.5 12.5c0.5 0.5 1.2 0.5 2.1 0.1l14.6-8.4" fill="#f73448"/><path d="m27.1 15.8l-14.6-8.3c-0.9-0.5-1.6-0.4-2.1 0.1l12.5 12.4 4.2-4.2z" fill="#00f076"/></g><g fill="#fff"><path d="m47.9 8.8c0 0.6-0.2 1.1-0.5 1.4-0.5 0.5-1.1 0.7-2 0.7h-0.6v2.5h-1v-6.4c0.5-0.1 1.1-0.1 1.7-0.1 0.8 0 1.5 0.2 1.9 0.5 0.3 0.3 0.5 0.8 0.5 1.4zm-1.1 0.1c0-0.4-0.1-0.7-0.4-0.9-0.2-0.2-0.6-0.3-1-0.3-0.3 0-0.5 0-0.7 0.1v2.3c0.1 0 0.4 0.1 0.6 0.1 0.5 0 0.8-0.1 1.1-0.3s0.4-0.6 0.4-1z"/><path d="m53.7 11c0 0.7-0.2 1.3-0.6 1.8s-1 0.7-1.7 0.7-1.2-0.2-1.7-0.7c-0.4-0.5-0.6-1-0.6-1.7s0.2-1.3 0.6-1.8 1-0.7 1.7-0.7c0.7 0 1.2 0.2 1.7 0.7 0.4 0.5 0.6 1 0.6 1.7zm-1.1 0.1c0-0.4-0.1-0.8-0.3-1.1-0.2-0.4-0.5-0.6-0.9-0.6s-0.7 0.2-1 0.6c-0.2 0.3-0.3 0.7-0.3 1.1s0.1 0.8 0.3 1.1c0.2 0.4 0.5 0.6 1 0.6 0.4 0 0.7-0.2 0.9-0.6 0.2-0.3 0.3-0.7 0.3-1.1z"/><path d="m59.8 11c0 0.8-0.2 1.4-0.7 1.9-0.4 0.4-0.9 0.6-1.4 0.6-0.7 0-1.2-0.3-1.5-0.8l-0.1 0.7h-0.9v-1.3-5.5h1v2.9c0.3-0.5 0.8-0.8 1.5-0.8 0.6 0 1 0.2 1.4 0.7 0.5 0.3 0.7 0.9 0.7 1.6zm-1.1 0.1c0-0.5-0.1-0.8-0.3-1.1s-0.5-0.5-0.9-0.5c-0.3 0-0.5 0.1-0.7 0.3s-0.3 0.4-0.4 0.7v0.3 0.8c0 0.3 0.1 0.6 0.3 0.8s0.5 0.3 0.8 0.3c0.4 0 0.7-0.1 0.9-0.4s0.3-0.7 0.3-1.2z"/><path d="m62.5 7.3c0 0.2-0.1 0.3-0.2 0.5-0.1 0.1-0.3 0.2-0.5 0.2s-0.3-0.1-0.4-0.2-0.2-0.3-0.2-0.5 0.1-0.3 0.2-0.4 0.3-0.2 0.5-0.2c0.2 0 0.3 0.1 0.5 0.2 0 0.1 0.1 0.2 0.1 0.4zm-0.1 6.1h-1v-4.7h1v4.7z"/><path d="m68.1 10.9v0.5h-3.1c0 0.5 0.2 0.8 0.5 1.1 0.3 0.2 0.6 0.3 1 0.3 0.5 0 0.9-0.1 1.3-0.2l0.2 0.7c-0.4 0.2-1 0.3-1.6 0.3-0.7 0-1.3-0.2-1.7-0.6s-0.6-1-0.6-1.7c0-0.7 0.2-1.3 0.6-1.8s1-0.8 1.6-0.8c0.7 0 1.2 0.3 1.5 0.8 0.2 0.3 0.3 0.8 0.3 1.4zm-1-0.3c0-0.3-0.1-0.6-0.2-0.8-0.2-0.3-0.5-0.4-0.8-0.4s-0.6 0.1-0.8 0.4c-0.2 0.2-0.3 0.5-0.3 0.8h2.1z"/><path d="m72.1 9.7h-0.3c-0.4 0-0.7 0.1-0.9 0.4-0.2 0.2-0.3 0.6-0.3 0.9v2.5h-1v-3.2-1.5h0.9v0.9c0.1-0.3 0.3-0.6 0.5-0.7 0.2-0.2 0.5-0.3 0.8-0.3h0.3v1z"/><path d="m76.8 13.4h-3.8v-0.6l1.9-2.5c0.1-0.2 0.3-0.4 0.6-0.8h-2.3v-0.8h3.6v0.6l-1.8 2.4c-0.2 0.3-0.4 0.5-0.6 0.8h2.5l-0.1 0.9z"/><path d="m87.4 8.7l-1.5 4.7h-1l-0.6-2c-0.2-0.5-0.3-1-0.4-1.5-0.1 0.5-0.2 1-0.4 1.5l-0.6 2h-1l-1.4-4.7h1.1l0.5 2.2 0.3 1.5c0.1-0.4 0.2-0.9 0.4-1.5l0.7-2.2h0.9l0.6 2.2c0.2 0.5 0.3 1.1 0.4 1.6 0.1-0.5 0.2-1 0.3-1.6l0.6-2.2h1.1z"/></g></symbol><symbol id="google-plus" viewBox="0 0 28 18"><path d="M385.909,2756a9,9,0,0,0,0,18c5.142,0,8.553-3.65,8.553-8.79a8.417,8.417,0,0,0-.14-1.5h-8.413v3.09h5.053a4.827,4.827,0,0,1-5.053,3.88,5.682,5.682,0,0,1,0-11.36,5,5,0,0,1,3.551,1.38l2.418-2.35a8.515,8.515,0,0,0-5.969-2.35m16.546,5.14h-2.546v2.57h-2.545v2.57h2.545v2.58h2.546v-2.58H405v-2.57h-2.545v-2.57" transform="translate(-377 -2756)"/></symbol><symbol id="gplus-logo" viewBox="0 0 28 28"><path fill="inherit" d="m 24,14.86 h -1.82 v 1.71 h -1.82 v -1.71 h -1.81 v -1.72 h 1.82 v -1.71 h 1.82 v 1.71 H 24 v 1.71 z M 10.36,20 A 6.18,6.18 0 0 1 4,14 6.18,6.18 0 0 1 10.36,8 6.31,6.31 0 0 1 14.62,9.57 L 12.9,11.14 A 3.7,3.7 0 0 0 10.36,10.21 3.87,3.87 0 0 0 6.42,14 3.87,3.87 0 0 0 10.37,17.79 3.39,3.39 0 0 0 14,15.2 h -3.64 v -2.06 h 6 a 5.23,5.23 0 0 1 0.1,1 C 16.47,17.57 14,20 10.36,20 Z"/></symbol><symbol id="guidebook" viewBox="0 0 20.1 20"><path d="m13.2 9.5c-0.8-0.8-2.5-1.8-2.5-1.8s0.4-2.9 0-2.9-2.2 1.4-2.2 1.4c-1.7-0.7-2.9-0.7-2.9-0.7s0 1.2 0.7 2.9c0 0-1.4 1.8-1.4 2.2s2.9 0 2.9 0 1 1.7 1.8 2.5c0 0 0.7-2.9 0.4-3.3 0.4 0.5 3.2-0.3 3.2-0.3zm6.6 8.7-7.3-7.3c-0.4-0.4-1.1-0.4-1.5 0s-0.4 1.1 0 1.5l7.3 7.3c0.4 0.4 1.1 0.4 1.5 0s0.4-1.1 0-1.5zm-12.4-14.9s0.6 1 1 1.5c0 0 0.4-1.7 0.2-1.9 0.2 0.2 1.9-0.2 1.9-0.2-0.5-0.5-1.5-1-1.5-1s0.3-1.7 0-1.7c-0.2 0-1.2 0.8-1.2 0.8-1-0.4-1.7-0.4-1.7-0.4s0 0.7 0.4 1.7c0 0-0.8 1-0.8 1.2 0 0.3 1.7 0 1.7 0zm7.2 3.2s0.6 1 1 1.5c0 0 0.4-1.7 0.2-1.9 0.2 0.2 1.9-0.2 1.9-0.2-0.5-0.5-1.5-1-1.5-1s0.2-1.7 0-1.7-1.3 0.8-1.3 0.8c-1-0.4-1.7-0.4-1.7-0.4s0 0.7 0.4 1.7c0 0-0.8 1-0.8 1.3 0.1 0.2 1.8-0.1 1.8-0.1zm-7.2 7.1c-0.2 0-1.3 0.8-1.3 0.8-0.9-0.4-1.6-0.4-1.6-0.4s0 0.7 0.4 1.7c0 0-0.8 1-0.8 1.3 0 0.2 1.7 0 1.7 0s0.6 1 1 1.5c0 0 0.4-1.7 0.2-1.9 0.2 0.2 1.9-0.2 1.9-0.2-0.5-0.5-1.5-1-1.5-1s0.3-1.8 0-1.8zm-2.5-6.1c-0.5-0.5-1.5-1-1.5-1s0.2-1.7 0-1.7-1.3 0.8-1.3 0.8c-1-0.4-1.7-0.4-1.7-0.4s0 0.7 0.4 1.7c0 0-0.8 1-0.8 1.3 0 0.2 1.7 0 1.7 0s0.6 1 1 1.5c0 0 0.4-1.7 0.2-1.9 0.3 0.2 2-0.3 2-0.3z"/></symbol><symbol id="hamburger" viewBox="0 0 40 34"><path d="M20,32H60v6H20V32Zm0,14H60v6H20V46Zm0,14H60v6H20V60Z" transform="translate(-20 -32)"/></symbol><symbol id="infographic" viewBox="0 0 13 17"><path d="M 0,17 H 3 V 0 H 0 v 17 z m 5,0 H 8 V 8 H 5 v 9 z M 10,3 v 14 h 3 V 3 h -3 z"/></symbol><symbol id="instagram-v2" viewBox="0 0 30 30"><path d="M 15 3 C 11.75 3 11.340547 2.9996094 10.060547 3.0996094 A 8.73 8.73 0 0 0 7.1601562 3.6601562 A 5.53 5.53 0 0 0 5 5 A 5.77 5.77 0 0 0 3.6308594 7.1601562 A 8.73 8.73 0 0 0 3.0703125 10.060547 C 3.0003125 11.340547 3 11.75 3 15 C 3 18.25 2.9996094 18.660703 3.0996094 19.970703 A 8.73 8.73 0 0 0 3.6601562 22.869141 A 6.11 6.11 0 0 0 7.1601562 26.369141 A 8.73 8.73 0 0 0 10.060547 26.929688 C 11.340547 26.999688 11.75 27 15 27 C 18.25 27 18.659453 27.000391 19.939453 26.900391 A 8.73 8.73 0 0 0 22.839844 26.339844 A 6.1 6.1 0 0 0 26.339844 22.839844 A 8.73 8.73 0 0 0 26.900391 19.939453 C 27.000391 18.659453 27 18.25 27 15 C 27 11.75 27.000391 11.339297 26.900391 10.029297 A 8.73 8.73 0 0 0 26.339844 7.1308594 A 6.11 6.11 0 0 0 22.839844 3.6308594 A 8.73 8.73 0 0 0 19.939453 3.0703125 C 18.659453 3.0003125 18.25 3 15 3 z M 15 5.1699219 C 18.2 5.1699219 18.579844 5.2402344 19.839844 5.2402344 A 6.48 6.48 0 0 1 22.060547 5.6601562 A 3.69 3.69 0 0 1 23.439453 6.5507812 A 3.84 3.84 0 0 1 24.339844 7.9296875 A 6.71 6.71 0 0 1 24.75 10.150391 C 24.82 11.420391 24.820312 11.790234 24.820312 14.990234 C 24.820312 18.190234 24.8 18.570078 24.75 19.830078 A 6.71 6.71 0 0 1 24.339844 22.050781 A 4 4 0 0 1 22.060547 24.330078 A 6.71 6.71 0 0 1 19.839844 24.740234 C 18.579844 24.810234 18.2 24.810547 15 24.810547 C 11.8 24.810547 11.430156 24.790234 10.160156 24.740234 A 6.71 6.71 0 0 1 7.9394531 24.330078 A 3.84 3.84 0 0 1 6.5605469 23.429688 A 3.69 3.69 0 0 1 5.6699219 22.050781 A 6.48 6.48 0 0 1 5.25 19.830078 C 5.18 18.570078 5.1796875 18.190234 5.1796875 14.990234 C 5.1796875 11.790234 5.25 11.410391 5.25 10.150391 A 6.48 6.48 0 0 1 5.6699219 7.9296875 A 3.69 3.69 0 0 1 6.5605469 6.5507812 A 3.84 3.84 0 0 1 7.9394531 5.6503906 A 6.71 6.71 0 0 1 10.160156 5.2402344 C 11.430156 5.1702344 11.8 5.1699219 15 5.1699219 z "/><path d="M 14.744141 8.8554688 A 6.15 6.15 0 0 0 15 21.150391 A 6.15 6.15 0 0 0 21.150391 15 A 6.15 6.15 0 0 0 14.744141 8.8554688 z M 15 11 A 4 4 0 0 1 19 15 A 4 4 0 1 1 15 11 z "/><path d="m 21.39,10.05 a 1.44,1.44 0 1 1 1.44,-1.44 1.43,1.43 0 0 1 -1.44,1.44 z"/></symbol><symbol id="instagram" viewBox="0 0 19.8 19.4"><path class="st0" d="M17.3,0H2.6C1.2,0,0,1.1,0,2.5v14.4c0,1.4,1.2,2.5,2.5,2.5h14.7c1.4,0,2.5-1.1,2.5-2.5V2.5 C19.8,1.1,18.7,0,17.3,0z M14.2,3C14.2,3,14.2,3,14.2,3c0-0.4,0.3-0.7,0.8-0.7h1.8c0.4,0,0.7,0.3,0.8,0.7v1.8c0,0.4-0.3,0.7-0.8,0.7 h-1.8c0,0,0,0,0,0c-0.4,0-0.7-0.3-0.7-0.7V3z M9.9,5.5L9.9,5.5L9.9,5.5L9.9,5.5c2.3,0,4.1,1.9,4.1,4.2s-1.9,4.1-4.2,4.1 c-2.3,0-4.1-1.9-4.1-4.2C5.7,7.4,7.6,5.5,9.9,5.5z M17.9,16.7c0,0.4-0.4,0.8-0.8,0.8H2.7c-0.4,0-0.8-0.3-0.8-0.8V8.3h2 C3.8,8.7,3.8,9.2,3.8,9.7c0.1,3.3,2.7,6,6,6c3.4,0.1,6.2-2.6,6.3-6c0-0.5-0.1-0.9-0.2-1.4h2.1h0V16.7z"/></symbol><symbol id="interview-2" viewBox="0 0 30 30"><path d="m9.01,18.9a18.312,18.312,0,0,1-5.04,2.04c-1.05.21-.73-0.08-0.47-0.29a9.884,9.884,0,0,0,2.74-2.87,5.366,5.366,0,0,1-2.97-4.43c0-3.27,3.95-5.92,8.82-5.92s8.82,2.65,8.82,5.92-3.95,5.92-8.82,5.92Z m17.42,3.64a10.81,10.81,0,0,1-3.28-1.5,8.077,8.077,0,0,1-2.42.37,6.77,6.77,0,0,1-4.8-1.75c3.33-.97,5.66-3.2,5.88-5.89,2.66,0.34,4.66,1.9,4.66,3.78a3.317,3.317,0,0,1-1.52,2.62c0.06,0.44.26,1.04,1.61,2.1Z"/></symbol><symbol id="interview" viewBox="0 0 20 16"><path d="m 13.3,10.4 c 1.2,0 2.2,-0.9 2.2,-2.1 V 2.1 C 15.5,1 14.5,0 13.3,0 H 2.2 C 1,0 0,0.9 0,2.1 v 6.2 c 0,1.1 1,2.1 2.2,2.1 H 3.6 L 4.5,13.9 9,10.4 h 4.3 z m 4.5,-7.6 h -1.4 l -0.1,7 c 0,1.3 -1.5,1.3 -1.5,1.3 H 9.4 L 8.1,12.5 H 11 l 4.4,3.5 0.9,-3.5 h 1.4 c 1.3,0 2.3,-1 2.3,-2.1 V 4.8 c 0,-1.1 -1,-2 -2.2,-2 z"/></symbol><symbol id="left-arrow" viewBox="0 0 19.200001 32"><path d="M17.13 32l2.07-1.93L4.15 16 19.2 1.94 17.13 0 0 16"/></symbol><symbol id="letter" viewBox="0 0 29 21"><path d="M 0,18.6 C 0,19.9 1.1,21 2.4,21 H 26.6 C 27.9,21 29,19.9 29,18.6 V 2.4 C 29,1.1 27.9,0 26.6,0 H 2.4 C 1.1,0 0,1.1 0,2.4 V 18.6 z M 8.9,10.5 3.4,5 C 2.9,4.5 2.9,3.8 3.4,3.4 3.9,2.9 4.6,2.9 5.1,3.4 l 8.8,8.8 c 0.4,0.3 0.9,0.3 1.3,0 L 24,3.4 c 0.5,-0.5 1.2,-0.5 1.7,0 0.5,0.5 0.5,1.2 0,1.6 l -5.6,5.5 5.5,5.5 c 0.5,0.5 0.5,1.2 0,1.6 -0.5,0.5 -1.2,0.5 -1.7,0 l -5.5,-5.5 c 0,0 -1.7,1.7 -2,2.1 -0.5,0.5 -1.2,0.8 -1.9,0.8 -0.7,0 -1.4,-0.3 -1.9,-0.8 -0.3,-0.3 -2,-2 -2,-2 l -5.5,5.5 c -0.5,0.5 -1.2,0.5 -1.7,0 -0.5,-0.5 -0.5,-1.2 0,-1.6 l 5.5,-5.6 z"/></symbol><symbol id="linkedin" viewBox="0 0 30 30"><path d="m8.89,25.56h-4.45v-14.31h4.45v14.31Zm-2.22-16.27h0a2.58,2.58,0,1,1,2.57-2.58Zm14.44,16.27v-6.96c0-1.66-.03-3.79-2.31-3.79s-2.69,1.81-2.69,3.67v7.08h-4.44v-14.31h4.26v1.96h0.06a4.7,4.7,0,0,1,4.22-2.32c4.5,0,5.33,2.97,5.33,6.83l0.02,7.84h-4.45Z"/></symbol><symbol id="magnifier" viewBox="0 0 28.1875 28.19375"><path d="M27.944 26.756l-6.852-6.852c1.838-2.113 2.95-4.87 2.95-7.883C24.043 5.39 18.65 0 12.023 0 5.388 0 0 5.395 0 12.02c0 6.628 5.395 12.023 12.02 12.023 3.015 0 5.77-1.113 7.884-2.95l6.852 6.85c.162.163.38.25.594.25.212 0 .43-.08.594-.25.325-.324.325-.862 0-1.187zM1.682 12.02c0-5.7 4.638-10.332 10.333-10.332 5.7 0 10.334 4.638 10.334 10.333 0 5.696-4.634 10.34-10.335 10.34-5.695 0-10.333-4.637-10.333-10.34z"/></symbol><symbol id="mail-v2" viewBox="0 0 34 34"><polygon points="5,9 17,20 29,9 29,25 5,25"/><polygon points="17,18.5 27.5,9 6.5,9"/></symbol><symbol id="newspaper" viewBox="0 0 20 16"><path d="M3894,2213h4v4h-4v-4Zm-5-3h9v2h-9v-2Zm0,3h4v1h-4v-1Zm0,2h4v1h-4v-1Zm11-8h-1v-2a0.979,0.979,0,0,0-1-1h-16a0.979,0.979,0,0,0-1,1v14a0.979,0.979,0,0,0,1,1h18a0.979,0.979,0,0,0,1-1v-11A0.979,0.979,0,0,0,3900,2207Zm-14.5,9v-10H3897v2h-10v10h-4v-12h1.5v10h1Zm13.5-7v9h-11v-9h11Z" transform="translate(-3881 -2204)"/></symbol><symbol id="obituaries" viewBox="0 0 32 8.4407187"><path d="M4.465 3.478c-1.342 0-2.712.94-2.712 2.108 0 .43.154.91.575 1.197.21.154.21.393-.03.652-.286.297-.698.536-1.12.536C.67 7.97 0 7.51 0 6.62c0-.498.335-1.552 1.648-2.52 1.39-1.025 2.577-1.37 3.938-1.456C8.46.402 10.788 0 12.484 0c1.85 0 3.497.575 4.33.89.748.28 3.65 1.467 5.7 2.34 2.157.87 4.015 1.398 5.29 1.398 1.015 0 2.49-.355 2.49-1.58 0-.452-.18-.758-.44-.94-.144-.115-.25-.288-.25-.44 0-.45.413-1.14 1.14-1.14C31.56.527 32 1.32 32 1.925c0 1.628-1.83 2.73-3.056 3.228-2.26 2.348-4.55 3.287-6.764 3.287-1.63 0-3.19-.43-5.26-1.16-1.562-.554-6.458-2.51-8.422-3.112-1.81-.546-2.845-.69-4.014-.69h-.02zM10.778.88c-1.504 0-2.93.74-3.63 1.83.852.02 2.145.203 4.292.96 2.146.708 6.103 2.356 7.482 2.826 1.543.488 3.01.9 4.474.9 1.64 0 3.095-.527 3.86-1.877-1.522.19-2.96-.05-5.076-.854-1.63-.604-6.113-2.616-7.627-3.114-1.274-.42-2.51-.67-3.746-.67h-.03z"/></symbol><symbol id="padlock-close" viewBox="0 0 28.1 37.5"><path d="M28.1,17H0v20.5h28.1V17z M15.8,28.1l0,5.9l-3.6,0v-5.9c-1.1-0.6-1.9-1.8-1.9-3.2c0-2,1.7-3.7,3.7-3.7 c2,0,3.7,1.7,3.7,3.7C17.7,26.3,17,27.5,15.8,28.1z"/><path d="M7.8,15.1c0-0.3-0.1-3.6,0.1-4.9c0.3-2.9,3.1-5.3,6.2-5.3c2.9,0,5.5,2.2,6.1,5.2c0.3,1.2,0.1,4.7,0.1,5h4.9 c0-0.3,0.1-5.1-0.5-7.1C23.4,3.4,19,0.1,14.1,0C9.4-0.1,5.2,2.9,3.5,7.5c-0.8,2.1-0.6,7.3-0.6,7.6H7.8z"/></symbol><symbol id="padlock-open" viewBox="0 0 42.6 37.5"><path d="M42.6,17H14.5v20.5h28.1V17z M30.3,28.1V34h-3.6v-5.9c-1.1-0.6-1.9-1.8-1.9-3.2c0-2,1.7-3.7,3.7-3.7 s3.7,1.7,3.7,3.7C32.2,26.3,31.5,27.5,30.3,28.1z"/><path d="M4.9,15.1c0-0.3-0.1-3.6,0.1-4.9c0.3-2.9,3.1-5.3,6.2-5.3c2.9,0,5.5,2.2,6.1,5.2c0.3,1.2,0.1,4.7,0.1,5h4.9 c0-0.3,0.1-5.1-0.5-7.1c-1.3-4.6-5.7-7.9-10.6-8C6.5-0.1,2.3,2.9,0.6,7.5C-0.2,9.6,0,14.8,0,15.1H4.9z"/></symbol><symbol id="padlock" viewBox="0 0 45 56"><path d="M 38,21 H 36.3 V 14.3 C 36.3,6.3 30.6,0 22.4,0 14.2,0 8.6,6.4 8.6,14.3 V 21 H 6.9 C 3.1,21 0,24.1 0,28 v 21 c 0,3.9 3.1,7 6.9,7 H 38.1 C 41.9,56 45,52.9 45,49 V 28 C 44.9,24.1 41.8,21 38,21 z M 12,14.3 c 0,-6 4,-10.8 10.3,-10.8 6.2,0 10.4,4.8 10.4,10.8 V 21 H 12 V 14.3 z M 41.4,49 c 0,1.9 -1.6,3.5 -3.5,3.5 H 6.8 C 4.9,52.5 3.3,50.9 3.3,49 V 28 c 0,-1.9 1.6,-3.5 3.5,-3.5 H 38 c 1.9,0 3.5,1.6 3.5,3.5 v 21 h -0.1 z m -19,-17.5 c -1.9,0 -3.5,1.6 -3.5,3.5 0,1.3 0.7,2.4 1.7,3 v 5.7 c 0,1 0.8,1.7 1.7,1.7 1,0 1.7,-0.8 1.7,-1.7 V 38 c 1,-0.6 1.7,-1.7 1.7,-3 0.2,-1.9 -1.4,-3.5 -3.3,-3.5 z"/></symbol><symbol id="post-delete" viewBox="0 0 14 14"><path d="M588.55,437a7,7,0,1,1,7-7A7,7,0,0,1,588.55,437Zm0-13a6,6,0,1,0,6,6A6,6,0,0,0,588.55,424Z" transform="translate(-581.55 -423)"/><polygon points="10.31 4.66 9.35 3.69 7 6.03 4.66 3.69 3.69 4.66 6.04 7 3.69 9.34 4.66 10.31 7 7.96 9.35 10.31 10.31 9.34 7.96 7 10.31 4.66"/></symbol><symbol id="questionnaire" viewBox="0 0 20 20"><path d="M2.5,2.5v15h15v-15H2.5z M8.8,13.5l-1.2-1.2L5.1,10l1.2-1.2l2.4,2.4l4.9-4.7l1.2,1.2L8.8,13.5z"/></symbol><symbol id="quiz" viewBox="0 0 15 19.5"><path d="m6.9 3.3c-0.1-0.2-0.1-0.5-0.1-0.7 0-0.3 0.1-0.5 0.2-0.6 0.1-0.2 0.3-0.3 0.5-0.3s0.4 0.1 0.5 0.2 0.2 0.3 0.2 0.6-0.1 0.6-0.2 0.9-0.3 0.6-0.5 0.8c-0.2 0.3-0.4 0.5-0.6 0.8-0.3 0.2-0.5 0.4-0.8 0.5v2.2h1.9v-1.5c0.6-0.4 1.1-0.9 1.5-1.5s0.5-1.3 0.5-2.1-0.2-1.5-0.7-1.9c-0.4-0.5-1-0.7-1.9-0.7-0.8 0-1.4 0.2-1.9 0.7-0.4 0.5-0.6 1-0.6 1.8 0 0.4 0.1 0.8 0.2 1.2l1.8-0.4zm7.4 3.4c-0.5-0.4-1.1-0.7-2-0.7-0.8 0-1.5 0.2-1.9 0.7s-0.6 1-0.6 1.8c0 0.4 0.1 0.8 0.2 1.2l1.8-0.3c-0.1-0.2-0.1-0.5-0.1-0.7 0-0.3 0.1-0.5 0.2-0.7s0.3-0.3 0.5-0.3 0.4 0.1 0.5 0.2 0.2 0.3 0.2 0.6-0.1 0.6-0.2 0.9-0.3 0.6-0.5 0.9-0.4 0.5-0.6 0.7-0.5 0.4-0.7 0.6v2.2h1.9v-1.6c0.6-0.4 1.1-0.9 1.5-1.5s0.5-1.3 0.5-2.1-0.3-1.4-0.7-1.9zm-3.3 9.9h1.9v-1.9h-1.9v1.9zm-3.6-7.6c-0.8 0-1.5 0.2-1.9 0.7-0.4 0.4-0.6 1-0.6 1.8 0 0.4 0.1 0.8 0.2 1.2l1.8-0.3c-0.1-0.2-0.1-0.5-0.1-0.7 0-0.3 0.1-0.5 0.2-0.7s0.3-0.3 0.5-0.3 0.4 0.1 0.5 0.2 0.2 0.3 0.2 0.6-0.1 0.6-0.2 0.9-0.3 0.6-0.5 0.9-0.4 0.5-0.6 0.7-0.5 0.4-0.7 0.6v2.2h1.9v-1.6c0.6-0.4 1.1-0.9 1.5-1.5s0.5-1.3 0.5-2.1-0.2-1.5-0.7-1.9c-0.5-0.5-1.2-0.7-2-0.7zm-1.3 10.5h1.9v-1.9h-1.9v1.9zm-4.9-3.9h1.9v-1.9h-1.9v1.9zm3.3-9.9c-0.5-0.4-1.1-0.6-2-0.6-0.8 0-1.5 0.2-1.9 0.7-0.4 0.4-0.6 1-0.6 1.8 0 0.4 0.1 0.8 0.2 1.2l1.8-0.3c-0.1-0.4-0.1-0.6-0.1-0.8 0-0.3 0.1-0.5 0.2-0.7s0.2-0.3 0.4-0.3 0.4 0.1 0.5 0.2 0.2 0.3 0.2 0.6-0.1 0.6-0.2 0.9c0 0.4-0.2 0.6-0.4 0.9s-0.4 0.5-0.6 0.7-0.5 0.4-0.7 0.6v2.2h1.9v-1.6c0.6-0.4 1.1-0.9 1.5-1.5s0.5-1.3 0.5-2.1-0.2-1.4-0.7-1.9z"/></symbol><symbol id="read-later" viewBox="0 0 34 34"><path d="M26,31l-9-8l-9,8V3h18V31z"/></symbol><symbol id="reply" viewBox="0 0 17 9"><path d="M0,4.5L5.7,0v2.2C15.9,2.2,17,7.9,17,9C14.7,4.5,6.8,6.8,5.7,6.8V9L0,4.5z"/></symbol><symbol id="right-arrow" viewBox="0 0 19.200001 32"><path d="M15.05 16L0 1.94 2.07 0 19.2 16 2.07 32 0 30.07"/></symbol><symbol id="snapchat" viewBox="0 0 21 20"><path d="M570.633,2775a0.86,0.86,0,0,1-.161-0.01h0c-0.034,0-.069.01-0.105,0.01a4.524,4.524,0,0,1-2.814-1.13,4.123,4.123,0,0,0-1.63-.84,4.9,4.9,0,0,0-.855-0.08,5.92,5.92,0,0,0-1.186.14,2.191,2.191,0,0,1-.441.07,0.282,0.282,0,0,1-.307-0.23c-0.049-.16-0.084-0.33-0.119-0.49a0.8,0.8,0,0,0-.32-0.69c-1.973-.31-2.538-0.73-2.664-1.02a0.479,0.479,0,0,1-.03-0.13,0.227,0.227,0,0,1,.186-0.24c3.033-.5,4.393-3.65,4.45-3.78l0-.02a1.175,1.175,0,0,0,.109-0.98,2.024,2.024,0,0,0-1.337-.86c-0.11-.04-0.214-0.07-0.3-0.1-0.9-.37-0.971-0.73-0.936-0.92a0.851,0.851,0,0,1,.824-0.55,0.531,0.531,0,0,1,.245.06,2.582,2.582,0,0,0,1.081.28,0.853,0.853,0,0,0,.645-0.21c-0.01-.2-0.024-0.42-0.038-0.65a10.884,10.884,0,0,1,.251-4.29,5.473,5.473,0,0,1,5.1-3.34h0.427a5.483,5.483,0,0,1,5.1,3.34,10.9,10.9,0,0,1,.251,4.29l0,0.06c-0.012.21-.024,0.4-0.034,0.59a0.891,0.891,0,0,0,.588.21h0a2.644,2.644,0,0,0,1.019-.29,0.853,0.853,0,0,1,.316-0.06,0.908,0.908,0,0,1,.364.07h0.007a0.682,0.682,0,0,1,.509.56,1.112,1.112,0,0,1-.944.84,1.728,1.728,0,0,1-.3.1,2.1,2.1,0,0,0-1.337.86,1.2,1.2,0,0,0,.109.99,0.042,0.042,0,0,0,0,.01c0.057,0.13,1.416,3.28,4.45,3.79a0.224,0.224,0,0,1,.186.23,0.336,0.336,0,0,1-.031.13c-0.125.3-.689,0.72-2.663,1.03a0.748,0.748,0,0,0-.32.68c-0.035.17-.07,0.33-0.119,0.49a0.266,0.266,0,0,1-.283.22H577.56a2.2,2.2,0,0,1-.442-0.06,6.738,6.738,0,0,0-1.186-.13,6.137,6.137,0,0,0-.856.08,4.093,4.093,0,0,0-1.627.84A4.534,4.534,0,0,1,570.633,2775Z" transform="translate(-560 -2755)"/></symbol><symbol id="spam" viewBox="0 0 11 14.5"><path d="M2.3,14.5h6.5c0.8,0,1.4-0.6,1.4-1.5V2.9H0.8v10.2C0.8,13.9,1.5,14.5,2.3,14.5z M7.7,4.7 c0-0.3,0.2-0.5,0.6-0.5c0.3,0,0.6,0.2,0.6,0.5v7.9c0,0.3-0.3,0.5-0.6,0.5s-0.6-0.2-0.6-0.5C7.7,12.6,7.7,4.7,7.7,4.7z M5,4.7	c0-0.3,0.3-0.5,0.6-0.5c0.3,0,0.6,0.2,0.6,0.5v7.9c0,0.3-0.3,0.5-0.6,0.5c-0.3,0-0.6-0.2-0.6-0.5V4.7z M2.2,4.7 c0-0.3,0.2-0.5,0.6-0.5s0.6,0.2,0.6,0.5v7.9c0,0.3-0.3,0.5-0.6,0.5s-0.6-0.2-0.6-0.5C2.2,12.6,2.2,4.7,2.2,4.7z"/><path d="M3.7,0h-2C0.8,0,0,0.8,0,1.7h11c0-1-0.8-1.7-1.8-1.7H3.7"/></symbol><symbol id="star" viewBox="0 0 15 14"><path d="M500.943,3644.22a1.056,1.056,0,0,0-.865-0.68l-3.9-.56-1.695-3.4a1.12,1.12,0,0,0-1.962,0l-1.7,3.4-3.9.56a1.058,1.058,0,0,0-.864.68,0.971,0.971,0,0,0,.251,1.03l2.861,2.75-0.664,3.82a0.981,0.981,0,0,0,.446.99,1.118,1.118,0,0,0,1.147.06l3.4-1.76,3.4,1.76a1.117,1.117,0,0,0,1.146-.06,0.98,0.98,0,0,0,.444-0.99l-0.663-3.82,2.86-2.75A0.964,0.964,0,0,0,500.943,3644.22Z" transform="translate(-486 -3639)"/></symbol><symbol id="survey" viewBox="0 0 20 20"><path d="m 8.9,2.1 0,0 c -5,0 -8.9,4 -8.9,9 0,4.9 4,9 8.9,9 4.9,0 8.9,-4 8.9,-9 -2.3,0 -8.9,0 -8.9,0 0,0 0,-6.6 0,-9 z M 20,9.6 C 20,4.3 15.7,0 10.4,0 V 9.7 L 20,9.6 z"/></symbol><symbol id="tweet" viewBox="0 0 28 24"><path d="M 28.1,2.8 C 27.1,3.3 25.9,3.6 24.8,3.7 26,3 26.9,1.8 27.4,0.4 26.3,1.1 25,1.6 23.7,1.9 22.6,0.7 21.1,0 19.5,0 c -3.2,0 -5.8,2.7 -5.8,6 0,0.5 0.1,0.9 0.2,1.4 C 9.1,7.1 4.8,4.8 1.9,1.1 1.4,2 1.1,3 1.1,4.1 c 0,2.1 1,3.9 2.6,5 -1,0 -1.9,-0.3 -2.6,-0.8 v 0.1 c 0,2.9 2,5.3 4.7,5.9 -0.5,0.1 -1,0.2 -1.5,0.2 -0.4,0 -0.7,0 -1.1,-0.1 0.7,2.4 2.9,4.1 5.4,4.2 -2,1.6 -4.5,2.6 -7.2,2.6 -0.5,0 -0.9,0 -1.4,-0.1 2.6,1.7 5.6,2.7 8.9,2.7 10.7,0 16.6,-9.2 16.6,-17.1 0,-0.3 0,-0.5 0,-0.8 0.8,-0.8 1.8,-1.9 2.6,-3.1 z"/></symbol><symbol id="twitter-v2" viewBox="0 0 34 34"><path d="M12.9,27.8c0,0-0.1,0-0.1,0c-5,0-8-2.4-8.1-2.5l-1.5-1.3l1.9,0.4c0,0,3,0.6,6.6-1.5C8.2,22,7.4,19,7.4,19
 +    l-0.2-0.7l0.7,0.1c0.1,0,0.2,0,0.3,0c-2.2-1.4-2.8-4-2.7-5l0.1-0.7L6.2,13c0.2,0.1,0.5,0.2,0.7,0.3c-1.1-1.3-2.2-3.6-0.7-6.7L6.6,6
 +    L7,6.6c3.9,4.4,8.6,5.4,10.2,5.4c-0.4-3.2,2-5.9,4.5-6.5c2.5-0.6,4.5,0.6,5.5,1.5l3.7-1.5c0,0-0.7,1.8-2.1,3.3l2.9-1.1l-0.4,0.9
 +    c0,0.1-0.8,1.3-2.2,2.5c0.1,5.6-2.8,10.4-5.3,12.6C20.7,26.5,17.2,27.8,12.9,27.8z"/></symbol><symbol id="up-arrow" viewBox="0 0 21.2 14.1"><rect x="-0.4" y="4.6" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -2.9289 7.0711)" width="15" height="5"/><rect x="6.6" y="4.6" transform="matrix(-0.7071 -0.7071 0.7071 -0.7071 19.1421 22.0711)" width="15" height="5"/></symbol><symbol id="user" viewBox="0 0 32 32"><path d="M8.4,23.9c-0.2,0-0.4-0.1-0.4-0.3c-0.1-0.2,0.1-0.5,0.3-0.5c1.6-0.4,3.9-1.2,4.4-1.7
 +        c0.1-0.2,0.1-1,0-1.9c-0.7-0.6-1.2-1.5-1.4-2.4c-0.2-0.9-0.5-2.5-0.6-3.3c-0.1-1-0.1-1.4,0-2.1l0-0.1c0.3-2.2,2.5-3.9,5.2-3.9
 +        c2.7,0,4.9,1.7,5.2,3.9l0,0.1c0.1,0.6,0.1,1.1,0,2.1c-0.1,0.8-0.4,2.5-0.6,3.3c-0.2,0.9-0.7,1.7-1.4,2.4c-0.1,0.8-0.1,1.7,0,1.9
 +        c0.5,0.5,3,1.3,4.5,1.7c0.2,0.1,0.4,0.3,0.3,0.5s-0.3,0.4-0.5,0.3c-0.7-0.2-4.2-1-5-2c-0.5-0.6-0.3-2.1-0.2-2.7
 +        c0-0.1,0.1-0.2,0.1-0.3c0.6-0.5,1-1.2,1.2-2c0.2-0.8,0.5-2.4,0.6-3.2c0.1-0.9,0.1-1.2,0-1.8l0-0.1C20,10,18.2,8.6,15.9,8.6
 +        c-2.2,0-4.1,1.3-4.3,3.1l0,0.1c-0.1,0.6-0.1,1,0,1.9c0.1,0.8,0.4,2.4,0.6,3.2c0.2,0.8,0.6,1.5,1.2,2c0.1,0.1,0.1,0.2,0.1,0.3
 +        c0.1,0.6,0.3,2.2-0.2,2.7c-0.8,0.9-4.1,1.8-4.8,2C8.5,23.9,8.4,23.9,8.4,23.9z"/><path d="M16,30.3c-7.7,0-13.9-6.2-13.9-13.9C2.1,8.8,8.3,2.5,16,2.5c7.7,0,13.9,6.2,13.9,13.9
 +        C29.9,24.1,23.7,30.3,16,30.3z M16,3.4c-7.2,0-13,5.8-13,13s5.8,13,13,13s13-5.8,13-13S23.2,3.4,16,3.4z"/></symbol><symbol id="video-round" viewBox="0 0 20 20"><path d="M10,20A10,10,0,1,1,20,10,10,10,0,0,1,10,20ZM10,.83A9.17,9.17,0,1,0,19.17,10,9.19,9.19,0,0,0,10,.83Z" style="fill:currentColor"/><polygon points="16.17 10 5.83 15.97 5.83 4.06 16.17 10"/></symbol><symbol id="video" viewBox="0 0 14.6 15.85"><path d="M 14.1,7.05 1.5,0.15 c -0.3,-0.2 -0.7,-0.2 -1,0 C 0.2,0.35 0,0.65 0,1.05 v 13.8 c 0,0.4 0.2,0.7 0.5,0.9 0.2,0.1 0.3,0.1 0.5,0.1 0.2,0 0.3,0 0.5,-0.1 l 12.6,-6.9 c 0.3,-0.2 0.5,-0.5 0.5,-0.9 0,-0.4 -0.2,-0.7 -0.5,-0.9 z"/></symbol><symbol id="vote-down" viewBox="0 0 14.44 14.11"><path d="M12.22-.24a0.66,0.66,0,0,0-.61.43v0l-1.45-.07A7.88,7.88,0,0,0,8-.9a13,13,0,0,0-4.42,0S2.1-.84,1.64,1.23c0,0-1.29,4.1.07,5.88,0,0,.62,1.16,3,0.93H5.51s-1.22,3.18.21,4.54c0,0,1.54,1,2.1-.1l0-.1C8.37,11.14,9.34,7.62,11.59,7h3.52s0.41,0,.41-0.6V0.57a0.87,0.87,0,0,0-.76-0.8H12.22Z" transform="translate(-1.09 1.11)"/></symbol><symbol id="vote-up" viewBox="0 0 14.44 14.11"><path d="M4.17,9.17a0.66,0.66,0,0,0,.61-0.43v0L6.24,8.77A7.88,7.88,0,0,0,8.39,9.83a13,13,0,0,0,4.42,0s1.47-.07,1.94-2.14c0,0,1.29-4.1-.07-5.88,0,0-.62-1.16-3-0.93H10.88s1.22-3.18-.21-4.54c0,0-1.54-1-2.1.1l0,0.1C8-2.21,7.05,1.31,4.79,1.89H1.27s-0.41,0-.41.6V8.36a0.87,0.87,0,0,0,.76.8H4.17Z" transform="translate(-0.86 4.06)"/></symbol><symbol id="windows-store" viewBox="0 0 135 40"><g><path class="store-frame" d="m130.2 40h-125.5c-2.6 0-4.7-2.1-4.7-4.7v-30.6c0-2.6 2.1-4.7 4.7-4.7h125.5c2.6 0 4.8 2.1 4.8 4.7v30.5c0 2.7-2.2 4.8-4.8 4.8z" fill="#A6A6A6"/><path d="m134 35.3c0 2.1-1.7 3.8-3.8 3.8h-125.5c-2.1 0-3.8-1.7-3.8-3.8v-30.6c0-2.1 1.7-3.8 3.8-3.8h125.5c2.1 0 3.8 1.7 3.8 3.8v30.6z" class="store-bkg"/></g><g fill="#fff"><path d="m63.5 20.8c0-0.4 0.1-0.7 0.4-1s0.6-0.4 1-0.4 0.8 0.1 1 0.4c0.3 0.3 0.4 0.6 0.4 1s-0.1 0.7-0.4 1-0.6 0.4-1 0.4-0.7-0.1-1-0.4c-0.2-0.3-0.4-0.7-0.4-1m2.6 12.7h-2.3v-9.8h2.3v9.8z"/><path d="m73.1 31.8c0.3 0 0.7-0.1 1.1-0.2l1.2-0.6v2.2c-0.4 0.2-0.8 0.4-1.3 0.5s-1 0.2-1.5 0.2c-1.5 0-2.6-0.5-3.5-1.4s-1.4-2.1-1.4-3.5c0-1.6 0.5-2.9 1.4-3.9s2.2-1.5 3.9-1.5c0.4 0 0.9 0.1 1.3 0.2s0.8 0.2 1.1 0.4v2.2c-0.4-0.3-0.7-0.5-1.1-0.6s-0.8-0.2-1.1-0.2c-0.9 0-1.7 0.3-2.2 0.9s-0.8 1.4-0.8 2.4c0 1 0.3 1.8 0.8 2.3 0.5 0.3 1.2 0.6 2.1 0.6"/><path d="m82 23.5h0.5c0.1 0 0.3 0.1 0.4 0.1v2.3c-0.1-0.1-0.3-0.2-0.5-0.3s-0.5-0.1-0.8-0.1c-0.6 0-1 0.2-1.4 0.7s-0.6 1.2-0.6 2.2v5h-2.3v-9.8h2.3v1.5c0.2-0.5 0.5-1 1-1.3 0.3-0.2 0.8-0.3 1.4-0.3"/><path d="m83 28.7c0-1.6 0.5-2.9 1.4-3.9s2.2-1.4 3.8-1.4c1.5 0 2.7 0.5 3.6 1.4s1.3 2.2 1.3 3.7c0 1.6-0.5 2.9-1.4 3.8-0.9 1-2.2 1.4-3.8 1.4-1.5 0-2.7-0.5-3.6-1.3-0.9-0.9-1.3-2.2-1.3-3.7m2.4-0.1c0 1 0.2 1.8 0.7 2.4s1.1 0.8 2 0.8c0.8 0 1.5-0.3 1.9-0.8s0.7-1.3 0.7-2.4c0-1.1-0.2-1.9-0.7-2.4s-1.1-0.8-1.9-0.8-1.5 0.3-2 0.8c-0.5 0.6-0.7 1.4-0.7 2.4"/><path d="m96.6 26.2c0 0.3 0.1 0.6 0.3 0.8s0.7 0.4 1.4 0.7c0.9 0.4 1.6 0.8 2 1.2 0.4 0.5 0.6 1 0.6 1.7 0 0.9-0.4 1.7-1.1 2.2-0.7 0.6-1.7 0.8-2.9 0.8-0.4 0-0.9 0-1.4-0.2-0.5-0.1-0.9-0.2-1.3-0.4v-2.3c0.4 0.3 0.9 0.5 1.4 0.7s0.9 0.3 1.3 0.3c0.5 0 0.9-0.1 1.2-0.2 0.2-0.1 0.4-0.4 0.4-0.7s-0.1-0.6-0.4-0.8-0.7-0.5-1.5-0.8c-0.9-0.4-1.5-0.8-1.8-1.2-0.4-0.5-0.5-1-0.5-1.7 0-0.9 0.4-1.6 1.1-2.2s1.6-0.9 2.7-0.9c0.3 0 0.7 0 1.2 0.1 0.4 0.1 0.8 0.2 1.1 0.3v2.4c-0.3-0.2-0.7-0.4-1.1-0.5s-0.8-0.2-1.2-0.2-0.8 0.1-1 0.3c-0.4 0.1-0.5 0.3-0.5 0.6"/><path d="m101.8 28.7c0-1.6 0.5-2.9 1.4-3.9s2.2-1.4 3.8-1.4c1.5 0 2.7 0.5 3.6 1.4s1.3 2.2 1.3 3.7c0 1.6-0.5 2.9-1.4 3.8s-2.2 1.4-3.8 1.4c-1.5 0-2.7-0.5-3.6-1.3-0.9-0.9-1.3-2.2-1.3-3.7m2.4-0.1c0 1 0.2 1.8 0.7 2.4s1.1 0.8 2 0.8c0.8 0 1.5-0.3 1.9-0.8s0.7-1.3 0.7-2.4c0-1.1-0.2-1.9-0.7-2.4s-1.1-0.8-1.9-0.8-1.5 0.3-2 0.8c-0.5 0.6-0.7 1.4-0.7 2.4"/><path d="m119.6 25.5v5.1c0 1 0.2 1.8 0.7 2.3s1.2 0.8 2.2 0.8c0.3 0 0.7 0 1-0.1s0.6-0.1 0.7-0.2v-1.9c-0.1 0.1-0.3 0.2-0.5 0.2-0.2 0.1-0.3 0.1-0.5 0.1-0.5 0-0.8-0.1-1-0.4s-0.3-0.7-0.3-1.3v-4.6h2.3v-1.9h-2.3v-2.9l-2.3 0.7v2.2h-3.5v-1.2c0-0.6 0.1-1 0.4-1.3s0.6-0.5 1.1-0.5c0.2 0 0.5 0 0.7 0.1s0.3 0.1 0.4 0.2v-2 1c-0.2-0.1-0.4-0.1-0.6-0.1h-0.8c-1 0-1.9 0.3-2.6 1s-1 1.5-1 2.5v1.4h-1.6v1.9h1.6v8h2.3v-8l3.6-1.1z"/></g><g><polygon points="61.5 19.7 61.5 33.5 59.1 33.5 59.1 22.7 59.1 22.7 54.8 33.5 53.3 33.5 48.9 22.7 48.9 22.7 48.9 33.5 46.7 33.5 46.7 19.7 50.1 19.7 54 29.9 54.1 29.9 58.2 19.7" fill="#fff"/></g><g><rect y="6.1" x="11.1" height="13" width="13" fill="#F25022"/><rect y="6.1" x="25.5" height="13" width="13" fill="#7FBA00"/><rect y="20.5" x="11.1" height="13" width="13" fill="#00A4EF"/><rect y="20.5" x="25.5" height="13" width="13" fill="#FFB900"/></g><g fill="#fff"><path d="m46.1 13.7v-7.4h2.8c0.5 0 0.9 0 1.1 0.1 0.4 0.1 0.7 0.2 0.9 0.3 0.2 0.2 0.4 0.4 0.6 0.7 0.1 0.3 0.2 0.6 0.2 1 0 0.6-0.2 1.2-0.6 1.6s-1.1 0.7-2.2 0.7h-1.9v3h-0.9zm1-3.9h1.9c0.6 0 1.1-0.1 1.3-0.4 0.3-0.2 0.4-0.6 0.4-1 0-0.3-0.1-0.6-0.2-0.8-0.2-0.2-0.4-0.4-0.6-0.4s-0.5-0.1-0.9-0.1h-1.9v2.7z"/><path d="m52.5 11c0-1 0.3-1.7 0.8-2.2 0.5-0.4 1-0.6 1.7-0.6s1.3 0.2 1.8 0.7 0.7 1.1 0.7 2c0 0.7-0.1 1.2-0.3 1.6s-0.5 0.7-0.9 0.9-0.8 0.3-1.3 0.3c-0.7 0-1.4-0.2-1.8-0.7-0.4-0.4-0.7-1.1-0.7-2zm1 0c0 0.7 0.1 1.2 0.4 1.5s0.7 0.5 1.1 0.5 0.8-0.2 1.1-0.5 0.4-0.9 0.4-1.6-0.2-1.2-0.5-1.5-0.7-0.5-1.1-0.5c-0.5 0-0.8 0.2-1.1 0.5s-0.3 0.9-0.3 1.6z"/><path d="m59.4 13.7h-0.8v-7.4h0.9v2.6c0.4-0.5 0.9-0.7 1.5-0.7 0.3 0 0.6 0.1 0.9 0.2s0.5 0.3 0.7 0.6c0.2 0.2 0.3 0.5 0.4 0.9 0.1 0.3 0.2 0.7 0.2 1.1 0 0.9-0.2 1.6-0.7 2.1s-1 0.8-1.6 0.8-1.1-0.3-1.5-0.8v0.6zm0-2.8c0 0.6 0.1 1.1 0.3 1.4 0.3 0.5 0.7 0.7 1.2 0.7 0.4 0 0.7-0.2 1-0.5s0.4-0.9 0.4-1.5c0-0.7-0.1-1.2-0.4-1.5s-0.6-0.5-1-0.5-0.7 0.2-1 0.5-0.5 0.8-0.5 1.4z"/><path d="m64.3 7.3v-1h0.9v1h-0.9zm0 6.4v-5.4h0.9v5.3h-0.9z"/><path d="m70.3 11.9l0.9 0.1c-0.1 0.5-0.4 1-0.8 1.3s-0.9 0.5-1.5 0.5c-0.8 0-1.4-0.2-1.9-0.7s-0.7-1.2-0.7-2c0-0.9 0.2-1.6 0.7-2.1s1.1-0.7 1.8-0.7 1.3 0.2 1.8 0.7 0.7 1.2 0.7 2.1v0.2h-4c0 0.6 0.2 1 0.5 1.3s0.7 0.5 1.1 0.5c0.3 0 0.6-0.1 0.9-0.3 0.2-0.2 0.4-0.5 0.5-0.9zm-3-1.4h3c0-0.4-0.2-0.8-0.3-1-0.3-0.3-0.7-0.5-1.1-0.5s-0.8 0.1-1 0.4c-0.4 0.2-0.6 0.6-0.6 1.1z"/><path d="m72.4 13.7v-5.4h0.8v0.8c0.2-0.4 0.4-0.6 0.6-0.7s0.4-0.2 0.6-0.2c0.3 0 0.6 0.1 0.9 0.3l-0.3 0.8c-0.2-0.1-0.4-0.2-0.7-0.2-0.2 0-0.4 0.1-0.5 0.2-0.2 0.1-0.3 0.3-0.3 0.5-0.1 0.3-0.2 0.7-0.2 1.1v2.8h-0.9z"/><path d="m75.3 13.7v-0.7l3.4-4h-1-2.2v-0.7h4.4v0.6l-2.9 3.4-0.6 0.6h1.1 2.5v0.8h-4.7z"/><path d="m83.3 13.7v-0.7l3.4-4h-1-2.2v-0.7h4.4v0.6l-2.9 3.4-0.6 0.6h1.1 2.5v0.8h-4.7z"/><path d="m92.8 13.7l-1.6-5.3h0.9l0.9 3.1 0.3 1.1c0-0.1 0.1-0.4 0.3-1.1l0.9-3.1h0.9l0.8 3.1 0.3 1 0.3-1 0.9-3.1h0.9l-1.7 5.3h-0.9l-0.9-3.2-0.2-0.9-1.1 4.1h-1z"/><path d="m99.3 7.3v-1h0.9v1h-0.9zm0 6.4v-5.4h0.9v5.3h-0.9z"/><path d="m103.6 12.9l0.1 0.8c-0.3 0.1-0.5 0.1-0.7 0.1-0.3 0-0.6-0.1-0.8-0.2s-0.3-0.2-0.4-0.4-0.1-0.5-0.1-1.1v-3.1h-0.7v-0.7h0.7v-1.3l0.9-0.5v1.9h0.9v0.6h-0.9v3.1 0.5c0 0.1 0.1 0.1 0.2 0.2 0.1 0 0.2 0.1 0.3 0.1h0.5z"/><path d="m104.4 13.7v-5.4h0.8v0.8c0.2-0.4 0.4-0.6 0.6-0.7s0.4-0.2 0.6-0.2c0.3 0 0.6 0.1 0.9 0.3l-0.3 0.8c-0.2-0.1-0.4-0.2-0.7-0.2-0.2 0-0.4 0.1-0.5 0.2-0.2 0.1-0.3 0.3-0.3 0.5-0.1 0.3-0.2 0.7-0.2 1.1v2.8h-0.9z"/><path d="m107.8 15.7l-0.1-0.9c0.2 0.1 0.4 0.1 0.5 0.1 0.2 0 0.4 0 0.5-0.1s0.2-0.2 0.3-0.3c0.1-0.1 0.1-0.3 0.3-0.7 0-0.1 0-0.1 0.1-0.2l-2-5.4h1l1.1 3.1c0.1 0.4 0.3 0.8 0.4 1.2 0.1-0.4 0.2-0.8 0.4-1.2l1.1-3.1h0.9l-2 5.4c-0.2 0.6-0.4 1-0.5 1.2-0.2 0.3-0.3 0.5-0.6 0.7-0.2 0.1-0.5 0.2-0.7 0.2-0.3 0.1-0.5 0.1-0.7 0z"/><path d="m113 13.7v-5.4h0.8v0.8c0.4-0.6 1-0.9 1.7-0.9 0.3 0 0.6 0.1 0.9 0.2s0.5 0.3 0.6 0.5 0.2 0.4 0.3 0.7c0 0.2 0.1 0.5 0.1 0.9v3.3h-0.9v-3.3c0-0.4 0-0.6-0.1-0.8s-0.2-0.3-0.4-0.4c-0.2-0.3-0.4-0.3-0.6-0.3-0.4 0-0.7 0.1-1 0.4-0.3 0.2-0.4 0.7-0.4 1.4v2.9h-1z"/><path d="m118.7 15.7l-0.1-0.9c0.2 0.1 0.4 0.1 0.5 0.1 0.2 0 0.4 0 0.5-0.1s0.2-0.2 0.3-0.3c0.1-0.1 0.1-0.3 0.3-0.7 0-0.1 0-0.1 0.1-0.2l-2-5.4h1l1.1 3.1c0.1 0.4 0.3 0.8 0.4 1.2 0.1-0.4 0.2-0.8 0.4-1.2l1.1-3.1h0.9l-2 5.4c-0.2 0.6-0.4 1-0.5 1.2-0.2 0.3-0.3 0.5-0.6 0.7s-0.5 0.2-0.7 0.2c-0.3 0.1-0.5 0.1-0.7 0z"/></g></symbol><symbol id="wyborcza-logo-square" viewBox="0 0 28 28"><rect fill="currentColor" width="12.22" height="18.84" x="3.56" y="4.58"/><polygon fill="inherit" points="26.73 10.38 25.81 10.38 24.16 16.62 24.03 16.61 22.14 11.2 23.26 10.54 23.26 10.38 18.55 10.38 18.55 10.54 19.29 11.21 22.25 19.53 23.95 19.53 25.31 14.44 25.45 14.44 27.25 19.53 28 19.53 28 14.03 26.73 10.38"/></symbol><symbol id="wyborcza-logo" viewBox="0 0 32.371646 48.557468"><path d="m 58.099892,473.7838 32.371645,0 0,-48.55747 -32.371645,0 0,48.55747 z"/></symbol></svg>
 +
 +<!-- svgModule v1.0.4 -->
 + +<!--10185197, [ null ], aggregatorModule--> +
 +
 +	 + + + + + +
 +
 +
 +
 +
 +
 +
 + + + + + + +
 + + + +<script type="text/javascript">
 +(function(){"undefined"!==typeof gazeta_pl&&"undefined"!==typeof gazeta_pl.mobileInfo&&gazeta_pl.mobileInfo.hasOwnProperty("isMobileDevice")&&(document.body.className+=!0===gazeta_pl.mobileInfo.isMobileDevice?" mobile_client":" desktop_client")})();
 +</script>
 +
 +<script>
 +
 +    
 +    
 +    
 +    var userStateCls = 'theme_white';
 +    
 +
 +var htmlTag = document.getElementsByTagName('html')[0];
 +htmlTag.className = htmlTag.className + ' ' + userStateCls;
 +</script>
 +
 +<div id="theme_holder" class="mcBan"></div>
 +
 + + + +<script> +    var wyborcza_pl = wyborcza_pl || {}; + +    wyborcza_pl.userInfo = { +            loggedIn: false, +            loggedInSoft: false, +            email: "", +            channel: "", +            nick: "", +            nickConfirmationPending: false +        }; +</script> + + + +     +         + + + + +<!-- x16 capParams: pl.com.agora.squid.portal2.dto.CapParameters@35596587[active=false,inProgress=false,validUntil=<null>,validPeriod=<null>,recurring=false,paymentUnfinished=false,unfinishedOrderId=<null>,offerId=<null>,lastPayment=<null>,inProgressOrderId=<null>,consentOrderId=<null>,defaultSalesProductId=true,capVisible=false,email=<null>,emailHash=<null>,marketingMessage=<null>,loginChannel=<null>,loginMode=<null>,nick=<null>,nickConfirmationPending=false,loggedIn=false,loggedInSoft=false]  --> + + +     + +        <nav id="wyborczaHat"> +            <div class="container-outer page-cap"> +                <div class="container-inner"> +                    <div class="grid-row"> +                        <div id="wH_container"> + +                            <section id="wH_nav"> +<div id="wH_logo"> +<a href="http://www.wyborcza.pl/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka2015', 'czapeczka_logo', 'klik_wyborcza.pl',,false]);"> +<span>Wyborcza.pl</span> +<img alt="Wyborcza.pl" src="https://static.im-g.pl/i/obrazki/wyborcza/wyborcza_pl.svg" data-fallback="https://bis.gazeta.pl/im/2/19701/m19701792.png" width="110" height="20" /> +</a> +</div> +<div id="wH_menu"> +<div id="wH_menu_icon"> +<svg class="badge-symbol"> +<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#burger"></use> +</svg> +</div> +<div id="wH_menu_wrapper"> +<div id="wH_links_nav"> +<header><h5>Wyborcza.pl</h5></header> +<ul> +</ul> +</div> +<div id="wH_links_main"> +<header><h5>Nasze serwisy</h5></header> +<ul> +<li class="wH_more"> +<span>Magazyny</span> +<div class="wH_links_sub"> +<ul> +<li> +<a href="http://wyborcza.pl/duzyformat/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Duy Format',,false]);"> +Duy Format +</a> +</li> +<li> +<a href="http://wyborcza.pl/magazyn/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Magazyn witeczny',,false]);"> +Magazyn witeczny +</a> +</li> +<li> +<a href="http://wyborcza.pl/alehistoria/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Ale Historia',,false]);"> +Ale Historia +</a> +</li> +<li> +<a href="http://wyborcza.pl/TylkoZdrowie/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Tylko Zdrowie',,false]);"> +Tylko Zdrowie +</a> +</li> +<li> +<a href="http://wyborcza.pl/ksiazki/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Ksiki. Magazyn do czytania',,false]);"> +Ksiki. Magazyn do czytania +</a> +</li> +<li> +<a href="http://wyborcza.pl/osiemdziewiec/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Osiem Dziewi',,false]);"> +Osiem Dziewi +</a> +</li> +<li> +<a href="http://wyborcza.pl/Opiekun/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Opiekun',,false]);"> +Opiekun +</a> +</li> +</ul> +</div> +</li> +<li> +<a href="http://wyborcza.biz/biznes/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Wyborcza.biz',,false]);">Wyborcza.biz</a> +</li> +<li> +<a href="http://wyborcza.pl/0,156282.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Wyborcza.Tech',,false]);">Wyborcza.Tech</a> +</li> +<li> +<a href="http://wyborcza.pl/0,160795.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Wyborcza Classic',,false]);">Wyborcza Classic</a> +</li> +<li> +<a href="http://wyborcza.pl/wiecejswiata/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Wicej wiata',,false]);">Wicej wiata</a> +</li> +<li> +<a href="http://wyborcza.pl/0,87647.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Witamy w Polsce',,false]);">Witamy w Polsce</a> +</li> +<li> +<a href="http://biqdata.wyborcza.pl/biqdata/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_BIQdata',,false]);">BIQdata</a> +</li> +<li> +<a href="http://sonar.wyborcza.pl/sonar/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Sonar',,false]);">Sonar</a> +</li> +<li> +<a href="http://www.wysokieobcasy.pl/wysokie-obcasy/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Wysokie Obcasy',,false]);">Wysokie Obcasy</a> +</li> +<li> +<a href="http://cojestgrane24.wyborcza.pl/cjg24/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Co Jest Grane 24',,false]);">Co Jest Grane 24</a> +</li> +<li> +<a href="http://www.logo24.pl/Logo24/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Logo 24',,false]);">Logo 24</a> +</li> +<li> +<a href="http://magazyn-kuchnia.pl/magazyn-kuchnia/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Magazyn Kuchnia',,false]);">Magazyn Kuchnia</a> +</li> +<li> +<a href="http://www.gazeta.pl/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Gazeta.pl',,false]);">Gazeta.pl</a> +</li> +<li class="wH_more"> +<span>Wiadomoci</span> +<div class="wH_links_sub"> +<ul> +<li> +<a href="http://wyborcza.pl/7,101707,24410372,michael-jackson-bral-z-dziecmi-sluby-na-niby-po-premierze.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Michael Jackson: Leaving Neverland',,false]);"> +Michael Jackson: Leaving Neverland +</a> +</li> +<li> +<a href="http://wyborcza.pl/7,75398,24418381,rada-mediow-narodowych-nie-odwola-kurskiego-wniosek-nie-zostal.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Rada Mediw Narodowych: Jarosaw Kurski',,false]);"> +Rada Mediw Narodowych: Jarosaw Kurski +</a> +</li> +<li> +<a href="http://warszawa.wyborcza.pl/warszawa/7,54420,24417372,reprywatyzacja-adwokat-robert-nowaczyk-zeznaje-przed-komisja.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Komisja reprywatyzacja: Robert Nowaczyk',,false]);"> +Komisja reprywatyzacja: Robert Nowaczyk +</a> +</li> +<li> +<a href="http://wyborcza.pl/7,75399,24416982,szesc-ofiar-dziennie-na-morzu-srodziemnym.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Morze rdziemne',,false]);"> +Morze rdziemne +</a> +</li> +<li> +<a href="http://wyborcza.pl/7,75399,24416239,wir-polarny-w-usa-sa-ofiary-smiertelne.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_USA: wir polarny',,false]);"> +USA: wir polarny +</a> +</li> +<li> +<a href="http://warszawa.wyborcza.pl/warszawa/7,54420,24415953,bialoleka-w-klebach-dymu-plonie-hala-ewakuowano-setke-osob.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Biaoka: hala na eraniu',,false]);"> +Biaoka: hala na eraniu +</a> +</li> +<li> +<a href="http://rzeszow.wyborcza.pl/rzeszow/7,34962,24416275,ponad-100-osob-zamknietych-w-sadzie-w-przemyslu-jest-informacja.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Przemyl: wglik',,false]);"> +Przemyl: wglik +</a> +</li> +<li> +<a href="http://wyborcza.pl/7,75398,24415430,tasmy-kaczynskiego-wiadomosci-tvp-o-pazernej-wyborczej.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Wiadomoci TVP: tamy Kaczyskiego',,false]);"> +Wiadomoci TVP: tamy Kaczyskiego +</a> +</li> +<li> +<a href="http://wyborcza.pl/7,75398,24415498,tasmy-kaczynskiego-nowe-nagranie-prezesa-pis-beda-mowic-ze.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Tamy Kaczyskiego',,false]);"> +Tamy Kaczyskiego +</a> +</li> +<li> +<a href="http://wyborcza.pl/7,75398,24415440,w-sejmie-debata-o-nbp-poslanka-po-zablokowala-mownice.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Sejm: NBP',,false]);"> +Sejm: NBP +</a> +</li> +<li> +<a href="http://wyborcza.pl/7,75398,24415454,tasmy-kaczynskiego-sprawdzamy-czy-prezes-pis-zlamal-prawo.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Jarosaw Kaczyski: tamy Kaczyskiego',,false]);"> +Jarosaw Kaczyski: tamy Kaczyskiego +</a> +</li> +<li> +<a href="http://wyborcza.pl/7,75398,24415443,tasmy-kaczynskiego-partia-buduje-wiezowiec-drugie-nagranie.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Tamy Kaczyskiego - drugie nagranie audio i stenogram',,false]);"> +Tamy Kaczyskiego - drugie nagranie audio i stenogram +</a> +</li> +<li> +<a href="http://wyborcza.pl/7,155287,24414148,europejski-raport-o-smogu-polska-walczy-za-slabo.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Smog',,false]);"> +Smog +</a> +</li> +<li> +<a href="http://wyborcza.pl/7,155287,24414565,zlote-dziecko-pis-na-zakrecie-kariery.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Micha Krupiski: tamy Kaczyskiego',,false]);"> +Micha Krupiski: tamy Kaczyskiego +</a> +</li> +<li> +<a href="http://krakow.wyborcza.pl/krakow/7,44425,24415702,wysadzony-bankomat-przy-ul-teligi-powazne-utrudnienia.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Bankomat: ul. Tegli w Krakowie',,false]);"> +Bankomat: ul. Tegli w Krakowie +</a> +</li> +<li> +<a href="http://wyborcza.pl/7,75398,24415472,tasmy-kaczynskiego-pekao-sa-nie-zaprzecza-ze-prezes-krupinski.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Tamy Kaczyskiego: Pekao SA, Micha Krupiski',,false]);"> +Tamy Kaczyskiego: Pekao SA, Micha Krupiski +</a> +</li> +<li> +<a href="http://krakow.wyborcza.pl/krakow/7,44425,24412575,smiertelny-wypadek-w-babicach-kierowca-zginal-na-obwodnicy.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Wypadek w Babicach',,false]);"> +Wypadek w Babicach +</a> +</li> +<li> +<a href="http://poznan.wyborcza.pl/poznan/7,36001,24413925,prezydent-poznania-jacek-jaskowiak-w-szpitalu-na-prawicowych.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Jacek Jakowiak',,false]);"> +Jacek Jakowiak +</a> +</li> +<li> +<a href="http://krakow.wyborcza.pl/krakow/7,44425,24412932,tatry-pod-rysami-zeszla-lawina.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Tatry: lawina pod Rysami',,false]);"> +Tatry: lawina pod Rysami +</a> +</li> +<li> +<a href="http://warszawa.wyborcza.pl/warszawa/7,54420,24411986,pierwszy-pociag-metra-dojechal-na-targowek-kiedy-otwarcie.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Metro Targwek',,false]);"> +Metro Targwek +</a> +</li> +<li> +<a href="http://wyborcza.pl/7,75398,24408844,tasmy-kaczynskiego-partia-buduje-wiezowiec-nagranie-audio.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Tamy Kaczyskiego: audio i stenogram',,false]);"> +Tamy Kaczyskiego: audio i stenogram +</a> +</li> +<li> +<a href="http://wyborcza.pl/alehistoria/7,162090,24414810,31-stycznia-niemcy-do-amerykanow-dajcie-nam-trzy-miesiace.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_31 stycznia',,false]);"> +31 stycznia +</a> +</li> +<li> +<a href="http://wyborcza.pl/TylkoZdrowie/7,157387,24415511,pogoda-w-czwartek-31-stycznia-2019-r.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Pogoda w czwartek 31 stycznia 2019 r.',,false]);"> +Pogoda w czwartek 31 stycznia 2019 r. +</a> +</li> +</ul> +</div> +</li> +<li class="wH_more"> +<span>Serwisy lokalne</span> +<div class="wH_links_sub"> +<ul> +<li> +<a href="http://bialystok.wyborcza.pl/bialystok/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Biaystok',,false]);"> +Biaystok +</a> +</li> +<li> +<a href="http://bielskobiala.wyborcza.pl/bielskobiala/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Bielsko-Biaa',,false]);"> +Bielsko-Biaa +</a> +</li> +<li> +<a href="http://bydgoszcz.wyborcza.pl/bydgoszcz/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Bydgoszcz',,false]);"> +Bydgoszcz +</a> +</li> +<li> +<a href="http://czestochowa.wyborcza.pl/czestochowa/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Czstochowa',,false]);"> +Czstochowa +</a> +</li> +<li> +<a href="http://gliwice.wyborcza.pl/gliwice/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Gliwice',,false]);"> +Gliwice +</a> +</li> +<li> +<a href="http://gorzow.wyborcza.pl/gorzow/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Gorzw Wielkopolski',,false]);"> +Gorzw Wielkopolski +</a> +</li> +<li> +<a href="http://katowice.wyborcza.pl/katowice/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Katowice',,false]);"> +Katowice +</a> +</li> +<li> +<a href="http://kielce.wyborcza.pl/kielce/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Kielce',,false]);"> +Kielce +</a> +</li> +<li> +<a href="http://krakow.wyborcza.pl/krakow/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Krakw',,false]);"> +Krakw +</a> +</li> +<li> +<a href="http://lublin.wyborcza.pl/lublin/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Lublin',,false]);"> +Lublin +</a> +</li> +<li> +<a href="http://lodz.wyborcza.pl/lodz/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_d',,false]);"> +d +</a> +</li> +<li> +<a href="http://olsztyn.wyborcza.pl/olsztyn/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Olsztyn',,false]);"> +Olsztyn +</a> +</li> +<li> +<a href="http://opole.wyborcza.pl/opole/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Opole',,false]);"> +Opole +</a> +</li> +<li> +<a href="http://plock.wyborcza.pl/plock/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Pock',,false]);"> +Pock +</a> +</li> +<li> +<a href="http://poznan.wyborcza.pl/poznan/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Pozna',,false]);"> +Pozna +</a> +</li> +<li> +<a href="http://radom.wyborcza.pl/radom/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Radom',,false]);"> +Radom +</a> +</li> +<li> +<a href="http://rzeszow.wyborcza.pl/rzeszow/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Rzeszw',,false]);"> +Rzeszw +</a> +</li> +<li> +<a href="http://sosnowiec.wyborcza.pl/sosnowiec/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Sosnowiec',,false]);"> +Sosnowiec +</a> +</li> +<li> +<a href="http://szczecin.wyborcza.pl/szczecin/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Szczecin',,false]);"> +Szczecin +</a> +</li> +<li> +<a href="http://torun.wyborcza.pl/torun/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Toru',,false]);"> +Toru +</a> +</li> +<li> +<a href="http://trojmiasto.wyborcza.pl/trojmiasto/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Trjmiasto',,false]);"> +Trjmiasto +</a> +</li> +<li> +<a href="http://warszawa.wyborcza.pl/warszawa/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Warszawa',,false]);"> +Warszawa +</a> +</li> +<li> +<a href="http://wroclaw.wyborcza.pl/wroclaw/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Wrocaw',,false]);"> +Wrocaw +</a> +</li> +<li> +<a href="http://zielonagora.wyborcza.pl/zielonagora/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Zielona Gra',,false]);"> +Zielona Gra +</a> +</li> +</ul> +</div> +</li> +<li class="wH_more"> +<span>Inne serwisy</span> +<div class="wH_links_sub"> +<ul> +<li> +<a href="http://www.tokfm.pl/Tokfm/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Tok FM',,false]);"> +Tok FM +</a> +</li> +<li> +<a href="http://www.edziecko.pl/edziecko/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Dziecko',,false]);"> +Dziecko +</a> +</li> +<li> +<a href="http://zdrowie.gazeta.pl/Zdrowie/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Zdrowie',,false]);"> +Zdrowie +</a> +</li> +<li> +<a href="http://www.blox.pl/html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Blogi',,false]);"> +Blogi +</a> +</li> +<li> +<a href="http://pogoda.gazeta.pl/" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Pogoda',,false]);"> +Pogoda +</a> +</li> +<li> +<a href="http://www.sport.pl/sport/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Sport',,false]);"> +Sport +</a> +</li> +<li> +<a href="http://podroze.gazeta.pl/podroze/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Podre',,false]);"> +Podre +</a> +</li> +<li> +<a href="http://forum.gazeta.pl/forum/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Forum',,false]);"> +Forum +</a> +</li> +<li> +<a href="http://forum.gazeta.pl/forum/0,0.html" onClick="_gaq.push(['_trackEvent', 'czapeczka_nowa', 'czapeczka_menu', 'klik_Forum',,false]);"> +Forum +</a> +</li> +</ul> +</div> +</li></ul> +</div> +</div> +</div> +</section><!-- UZREditor --><!--22573586,aliasOf--><!-- htmEOF -->
 + +                             +<div id="wH_messages"> + +     +         + +         + +         + +            <div id="wH_user_msg" class="mcBan"> +                <div id="wH_user_msg_content" data-cta="1sqbl-5basic" data-cta-category="czapeczka napis"> +                    <a href="http://prenumerata.wyborcza.pl/lp/0,145442.html?cta=1sqbl-2an-5basic">Wyprbuj od 19,90 z</a> +                </div> +            </div> +            <svg class="badge-symbol"> +                <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#bell"></use> +            </svg> +            <div id="wH_action" class="mcBan"> +                <a id="wH_action_btn" class="wH_btn" data-cta="1sqbl-2an-5basic" data-cta-category="czapeczka przycisk" href="http://prenumerata.wyborcza.pl/lp/0,145442.html?cta=1sqbl-2an-5basic">Kup teraz</a> +            </div> + +<!--             <div id="wH_user_msg" class="mcBan"> +                <div id="wH_user_msg_content" data-cta="1sqbl-5basic" data-cta-category="czapeczka napis"> +                    <a href="http://prenumerata.wyborcza.pl/lp/0,166591,24048616.html?cta=1sqbl-3an-550journalists?actionId=7518533d-2606-4905-bdaf-00a9eb088c7b&campaignId=4e827602-54e2-404d-8b18-d9e98d19ee3c">Twoje sprawy, nasza praca. Subskrybuj za p ceny i czytaj Wyborcza.pl</a> +                </div> +            </div> +            <svg class="badge-symbol"> +                <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#bell"></use> +            </svg> +            <div id="wH_action" class="mcBan"> +                <a id="wH_action_btn" class="wH_btn" data-cta="1sqbl-2an-5basic" data-cta-category="czapeczka przycisk" href="http://prenumerata.wyborcza.pl/lp/0,166591,24048616.html?cta=1sqbb-3an-550journalists?actionId=e246120c-339b-4506-ab5c-1a28c277b636&campaignId=4e827602-54e2-404d-8b18-d9e98d19ee3c">Skorzystaj z promocji -50%</a> +            </div> --> + +         +     + +</div> + +                             +<div id="wH_activity"> + +     +         + +         +            <div id="wH_user"> +                <form id="wH_login_form" method="POST"> +                    <input type="hidden" name="SsoSessionPermanent"> +                    <input type="hidden" name="host"> +                    <div id="wH_login_btn" class="hat-log-in" onclick="_gaq.push(['_trackEvent', 'CzapeczkaRWD', 'Logowanie', location.hostname, , true]);"> +                        Logowanie +                        <svg class="badge-symbol"> +                            <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#user"></use> +                        </svg> +                    </div> +                </form> +            </div> +         + +     + +</div> + + +                        </div> +                    </div> +                </div> +            </div> +        </nav> + +     +     + + + +     +     + + +<!-- squidCapBean v1.1 --> + +<!--9638935, [ /fix/modules/wyborcza/cap/cap.jsp ], squidCapBean--> +
 +    
 +<script type="text/javascript">
 +    //<![CDATA[
 +        try {
 +            var domain_marketing_params = {
 +                counter: null,
 +                disable: false,
 +                differentiateDomainMarketing: 'wsj2017biz',
 +                source: '',
 +                tags: ["jako powietrza","smog","wgiel","zanieczyszczenie powietrza"],
 +                clsarticle: '',
 +                marketingDomainRequestParams: '',
 +                isActive: 'false',
 +                validPeriod: '',
 +                subnetCompany: '',
 +                freeActivation: 'unknown',
 +                agreementState: 'false',
 +                loginChannel: '',
 +                dmcampaign: '',
 +                hardPaywall: true,
 +                recoveryMethod: '',
 +                rodo22: 'false'
 +            };
 +        } catch (e) { };
 +    //]]>
 +</script>
 +
 +<script>
 +    customDataLayer = window.customDataLayer || {};
 +    customDataLayer.customer = customDataLayer.customer || {};
 +    customDataLayer.customer.state = 'anonymous';
 +    customDataLayer.customer.validPeriod = '';
 +    document.cookie = 'customDataLayer_customer=' + encodeURIComponent(JSON.stringify(customDataLayer.customer)) + '; domain=wyborcza.pl; path=/';
 +</script>
 +
 +<!-- domain-marketing-module v1.0 -->
 + +<!--13289421, [ /fix/modules/wyborcza/portal/domainMarketingModule.jsp ], domainMarketingModule--> +
 +
 +    
 +
 +	<div class="container-outer page-top">
 +		<div class="container-inner">
 +			<div class="grid-row">
 +				<div class="grid-col-4-4 column-58">
 +                    
 +                        
 +                        
 +                            <div class="winieta-promo">
 +                                <header id="pageHead" data-like-shift="1"> +<div class="c0"> +<div class="imgw"> +<a id="LinkArea:winietabiz" href="http://wyborcza.biz/biznes/0,0.html" title="Wyborcza.biz"><img src="http://static.im-g.pl/i/obrazki/wyborcza2017/winiety_themes/wyborcza_biz.svg" data-fallback="https://bis.gazeta.pl/im/7/22716/m22716587.png" alt="Wyborcza.biz"></a> +</div> +<div class="header-date" id="header-day"><span class="day" id="header-date-day"></span><span class="date" id="header-date-date"></span></div> +<script> +(function() { +var a=document.getElementById("header-date-day"),b=document.getElementById("header-date-date"),c=(now && now.getUTCDate()>1)?new Date(now):new Date,d="niedziela poniedzia\u0142ek wtorek \u015broda czwartek pi\u0105tek sobota".split(" ")[c.getDay()],e=c.getDate(),f=c.getMonth()+1,g=c.getFullYear(),h="";10>e&&(e="0"+e.toString());10>f&&(f="0"+f.toString());h=e+"."+f+"."+g;a.innerHTML=d;b.innerHTML=h; +})(); +</script> +</div> +</header><!-- UZREditor --><!-- htmEOF --> +<!--22776313, [ /htm/22776/j22776313.htm ], null--> +
 +                                
 +
 +
 +<div id="071-WINIETA" class="adviewDFPBanner DFP-071-WINIETA">
 +    <span class="banLabel" style="display: none;">REKLAMA</span>
 +	<div id='div-gpt-ad-071-WINIETA-0'>
 +    <script type='text/javascript'>
 +    	if(dfpParams.slots['071-WINIETA'] && dfpParams.slots['071-WINIETA'].autoLoad) {
 +    		if (adviewDFP && adviewDFP.scrollSlot) adviewDFP.scrollSlot.push('071-WINIETA'); 
 +		} else {
 +		    googletag.cmd.push(function() { googletag.display('div-gpt-ad-071-WINIETA-0'); });
 +		}
 +       
 +    </script>
 +    </div>
 +</div>
 +
 +
 +<!-- v2.2 --> +<!--11070029, [ /tpl/ads/prod/dfpSlot.jsp ], dfpBanersSlotBean--> +
 +                            </div>
 +                        
 +                    
 +					
 +
 +
 +
 +
 +
 +<!-- Menubar20_nst API ver 1.0 -->
 +
 +	<!-- ver 0.45 newMenuBar:false, menuType: 0, nst:, szukaj:false, szukajNSTAlias:null -->
 +	
 +		<nav data-position="nawigacja" id="navH" class="level0"><ul class='p0'><li id='e1' class=' '><a href='http://wyborcza.pl/0,155287.html#TRNavSST' title='Aktualnoci '>Aktualnoci</a></li><li id='e2' class=' '><a href='http://wyborcza.biz/biznes/0,147768.html#TRNavSST' title='Podatki '>Podatki</a></li><li id='e3' class=' '><a href='http://wyborcza.biz/biznes/0,147582.html#TRNavSST' title='Finanse osobiste '>Finanse osobiste</a></li><li id='e4' class=' '><a href='http://wyborcza.biz/biznes/0,147880.html#TRNavSST' title='ZUS i emerytury '>ZUS i emerytury</a></li><li id='e5' class=' '><a href='http://wyborcza.biz/Gieldy/0,114514.html#TRNavSST' title='Gieda '>Gieda</a></li><li id='e6' class=' '><a href='http://wyborcza.biz/Waluty/0,111138,8932151,,,Kursy_srednie_walut_NBP,A.html#TRNavSST' title='Kursy walut '>Kursy walut</a></li><li id='e7' class='  active'><a href='http://wyborcza.biz/biznes/0,147743.html#TRNavSST' title='Konsument i zakupy '>Konsument i zakupy</a></li><li id='e8' class=' '><a href='http://wyborcza.biz/pieniadzeekstra/0,0.html#TRNavSST' title='Pienidze Ekstra '>Pienidze Ekstra</a></li><li id='e9' class=' '><a href='http://wyborcza.biz/biznes/0,147758.html#TRNavSST' title='Nieruchomoci '>Nieruchomoci</a></li><li id='e10' class=' '><a href='http://wyborcza.biz/biznes/0,159911.html#TRNavSST' title='Praca '>Praca</a></li><li id='e11' class=' '><a href='http://www.komunikaty.pl/komunikaty/#TRNavSST' title='Komunikaty '>Komunikaty</a></li><li id='e12' class=' '><a href='##TRNavSST' title='Wicej '>Wicej</a><ul class='p1'><li id='e12_1' class=' '><a href='http://wyborcza.pl/0,155290.html#TRNavSST' title='Opinie '>Opinie</a></li><li id='e12_2' class=' '><a href='http://wyborcza.biz/biznes/0,163992.html#TRNavSST' title='Finanse maej firmy '>Finanse maej firmy</a></li><li id='e12_3' class=' '><a href='http://wyborcza.biz/biznes/0,147584.html#TRNavSST' title='Wasna firma '>Wasna firma</a></li><li id='e12_4' class=' '><a href='http://wyborcza.pl/0,156282.html#TRNavSST' title='Technologie '>Technologie</a></li><li id='e12_5' class=' '><a href='http://wyborcza.biz/biznes/0,156481.html#TRNavSST' title='Motoryzacja i podre '>Motoryzacja i podre</a></li><li id='e12_6' class=' '><a href='http://wyborcza.biz/akcjespecjalne/0,154807.html#TRNavSST' title='Przedsibiorca Roku '>Przedsibiorca Roku</a></li></ul></li></ul></nav>
 +		
 +
 +					<div class="mod mod_search" id="pageSearch"><div class="visible"><form><fieldset><input id="pageSearchQ"><input value="Szukaj" type="submit"></fieldset></form></div><div class="hidden"><form data-target="serwis" data-default method="get" action="http://wyborcza.biz/biznes/wyszukaj/artykul"><p><input type="hidden" data-query name="query"/><input value="" name="" type="hidden"></p></form></div></div> +<!--21407274, [ /htm/21407/n21407274.htm ], null--> +
 +				</div>
 +			</div>
 +		</div>
 +	</div>
 +
 +    
 +
 +	<section class="ads ads-top container-outer">
 +		<div class="container-inner">
 +			<div class="grid-row">
 +				<div class="grid-col-wide">
 +					
 +
 +
 +<div id="001-TOPBOARD" class="adviewDFPBanner DFP-001-TOPBOARD">
 +    <span class="banLabel" style="display: none;">REKLAMA</span>
 +	<div id='div-gpt-ad-001-TOPBOARD-0'>
 +    <script type='text/javascript'>
 +    	if(dfpParams.slots['001-TOPBOARD'] && dfpParams.slots['001-TOPBOARD'].autoLoad) {
 +    		if (adviewDFP && adviewDFP.scrollSlot) adviewDFP.scrollSlot.push('001-TOPBOARD'); 
 +		} else {
 +		    googletag.cmd.push(function() { googletag.display('div-gpt-ad-001-TOPBOARD-0'); });
 +		}
 +       
 +    </script>
 +    </div>
 +</div>
 +
 +
 +<!-- v2.2 --> +<!--10185259, [ /tpl/ads/prod/dfpSlot.jsp ], dfpBanersSlotBean--> +
 +				</div>
 +			</div>
 +		</div>
 +	</section>
 +
 +	<main class="content container-inner">
 +        
 +            
 +            
 +                <div class="grid-row">
 +                    <div class="grid-col-wide">
 +                        
 +
 +
 +
 +
 +
 +
 +    
 +
 +    
 +        <header id="art-header" class="art-header ">
 +            <div id="art-tags" class="art-tags">
 +                
 +                    
 +                        
 +                            
 +                            
 +                                <a href="http://wyborcza.biz/biznes/0,147743.html" title="Konsument i zakupy"
 +                                   class="art-headline-tag"> <span class="art-tag-label">Konsument i zakupy</span></a>
 +                            
 +                        
 +                    
 +                    
 +                
 +            </div>
 +
 +            <div class="art-headline" role="headline">
 +                
 +                    
 +                    
 +                
 +                <h1 class="art-title">Pomys na biznes: chusta, ktra chroni przed smogiem</h1>
 +            </div>
 +
 +            <div class="art-header-meta">
 +                
 +                    
 +                    <div class="art-authors">
 +                        <span class="art-author">Micha Frk</span>
 +                    </div>
 +                
 +                
 +                
 +                <div class="art-author-meta-container">
 +                    
 +                    <time id="art-datetime" class="art-datetime"
 +                          datetime="2019-01-31">31 stycznia 2019 | 15:32</time>
 +                          
 +                </div>
 +            </div>
 +        </header>
 +    
 +
 +
 +<!-- articleMetadata v1.4 -->
 + +<!--9534510, [ /fix/modules/wyborcza/articleMetadata.jsp ], articleMetadata--> +
 +                    </div>
 +                </div>
 +            
 +        
 +        
 +            
 +                <div class="grid-row splint-parent">
 +            
 +            
 +        
 +            <section class="article-and-social grid-col-3-4" >
 +                <div class="article">
 +                    <article class="article-content">
 +                        
 +                            
 +                            
 +                                <div class="article-image">
 +                                
 +                                    
 +                                    
 +                                    
 +
 +
 +
 +
 +<!-- 0/4,24414148 --> +<!-- 1/4,24376685 --> +<!-- 2/4,24349296 --> +<!-- 3/4,24302580 --> +<!-- 0/4,24414148 --> +<!-- 1/4,24376685 --> +<!-- 2/4,24349296 --> +<!-- 3/4,24302580 --> +
 +
 +            
 +            
 +            
 +            
 +
 +            <div id="gazeta_article_image"><!-- rel max = 4 -->
 +            <div class="article-image-photo">
 +                 
 +                        <img border="0" src="https://bi.im-g.pl/im/f7/49/17/z24418295Q,Prace-nad-projektem-chusty-antysmogowej-rozpoczely.jpg" title="Prace nad projektem chusty antysmogowej rozpoczy si w lutym 2018 roku, a w styczniu 2019 pojawia si na rynku" alt="Prace nad projektem chusty antysmogowej rozpoczy si w lutym 2018 roku, a w styczniu 2019 pojawia si na rynku">
 +                        <figure class="box-label-related"><svg class="icon"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#gallery"></use></svg><figcaption>1 ZDJĘCIE</figcaption></figure>
 +                    
 +
 +                 
 +
 +                
 +
 +                 
 +                        </div>
 +                        <div class="article-image-desc">
 +                        <p class="desc">Prace nad projektem chusty antysmogowej rozpoczy si w lutym 2018 roku, a w styczniu 2019 pojawia si na rynku<span> (MATERIAY PRASOWE)</span></p>
 +
 +                 
 +                 
 +                    </div>
 +                 
 +            </div><!-- #gazeta_article_image -->
 +            
 +
 +  <!-- bl sstx  -->
 +                                
 +                                
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +<!-- plistaRelated v 1.04 -->
 + +<!--9638972, [ /fix/modules/plistaRelated.jsp ], null--> +
 +                                </div>
 +                            
 +                        
 +
 +                         + + + +<section class="article-lead">Filtr ma tak dokadny, e zatrzymuje nawet wirusy i bakterie. Trjka wrocawian stworzya chust, ktra chroni przed smogiem. </section> + + + + + + +     +        <section class="article-text"> + +            
 +
 +
 +<!-- SQUID_PARAMS: SquidPaywallLock [subnetAccessGranted=false, cookieDisabled=false, javaScriptDisabled=false, referrer=null, saleInProgress=false, paymentUnfinished=false, consentOrderId=null, unfinishedOrderId=null, validUntil=null, loggedInHard=false, loggedInSoft=false, counterShowIndicator=null, remainingVisitTime=null, exclusiveContent=true, loopholeDetected=false, isAssetUnlocked=false, closeByUrlParameter=null, aliasLock=/aliasy/dm/planbhardpaywallclosearticleall.htm, aliasUpper=, aliasLower=, aliasLink=, placeholders={standardLock=/aliasy/dm/planbclosearticleanonymous.htm, specialSectionLowerOpen=/aliasy/paywall/v12/empty.htm, standardLockUserLoggedIn=/aliasy/dm/planbclosearticleinactive.htm, activeOpenUpper=/aliasy/paywall/v12/empty.htm, upperOpen=/aliasy/dm/planbopenarticleupperzajawka.htm, subnetOpenLower=/aliasy/paywall/v12/empty.htm, closeByUrl=/aliasy/wejsciezmailingu.htm, loopholeDetected=/aliasy/dm/planbclosearticleloophole.htm, googleReferrerAnonymous=/aliasy/dm/planbopenarticlegoogleanonymous.htm, lowerOpenExclusive=/aliasy/paywall/v12/empty.htm, gazetaReferrerWeekendInactive=/aliasy/dm/planbclosearticlegaplweekend.htm, facebookReferrerAnonymous=/aliasy/dm/planbopenarticlefacebookanonymous.htm, cookieDisabled=/aliasy/wyborcza/nocookiesbox.htm, standardOpenUserLoggedIn=/aliasy/dm/pustazajawkav2.htm, gazetaReferrerNormalInactive=/aliasy/dm/planbopenarticlegaplpnpt.htm, specialSectionUpperOpen=/aliasy/paywall/v12/empty.htm, gazetaReferrerNormal=/aliasy/dm/planbopenarticlegaplpnpt.htm, subnetOpenApper=/aliasy/paywall/v12/empty.htm, activeOpenLower=/aliasy/paywall/v12/empty.htm, lowerOpen=/aliasy/dm/planbopenarticleanonymous.htm, exclusiveContentLock=/aliasy/dm/planbhardpaywallclosearticleall.htm, paymentFailed=/aliasy/paywall/v12/nieudana.jsp, serviceNotStarted=/aliasy/paywall/v12/uruchom.jsp, inProgressLock=/aliasy/paywall/v12/czekamy.htm, gazetaReferrerWeekend=/aliasy/dm/planbclosearticlegaplweekend.htm, specialSectionClose=/aliasy/dm/closearticleanonymousroot.htm, facebookReferrerInactive=/aliasy/dm/planbopenarticlefacebookinactive.htm, javascriptDisabled=/aliasy/paywall/v12/nojavascript.htm, upperOpenExclusive=/aliasy/dm/openarticleupperhardpaywall.htm, googleReferrerInactive=/aliasy/dm/planbopenarticlegoogleinactive.htm}, aliasEnabled=true, isPaywallException=false, hashedLogin=null, defaultSalesProductId=true] ; accessEnabled=false ; accessPaid=false ; locked=true -->
 +
 +<script type="text/javascript">
 +    //<![CDATA[
 +    try {
 +
 +        var pw_application_data = {
 +            UID: '',
 +            RemainingAccess: '',
 +            Alias: '/aliasy/dm/planbhardpaywallclosearticleall.htm',
 +            Paid: 'false',
 +            PaywallType: 'Hard',
 +            Access: 'false',
 +            AccessTo: ''
 +        };
 +
 +        var pw_article = {
 +            url: 'wyborcza.biz/biznes/7,147743,24417936,pomysl-na-biznes-chusta-ktora-chroni-przed-smogiem.html',
 +            title: '',
 +            lead: 'Filtr ma tak dokadny, e zatrzymuje nawet wirusy i bakterie. Trjka wrocawian stworzya chust, ktra chroni przed smogiem. '
 +        };
 +
 +        var wyborcza_pl = wyborcza_pl || {};
 +        wyborcza_pl.bigData = {
 +            EVENT_NAME: "pwdataready",
 +            init: function () {
 +                var a = "undefined" != typeof pw_application_data;
 +                a && this.createEvent()
 +            },
 +            createEvent: function () {
 +                var a = wyborcza_pl.bigData,
 +                    b = {};
 +                document.createEvent ? (b = document.createEvent("HTMLEvents"), b.initEvent(a.EVENT_NAME, !0, !0)) : (b = document.createEventObject(), b.eventType = a.EVENT_NAME), b.eventName = a.EVENT_NAME, document.createEvent ? document.dispatchEvent(b) : document.fireEvent("on" + b.eventType, b)
 +            }
 +        }, wyborcza_pl.bigData.init();
 +
 +    } catch (e) {
 +    }
 +    ;
 +    //]]>
 +</script>
 +
 +
 +
 +<script>
 +    //<![CDATA[
 +    var gazeta_pl = gazeta_pl || {};
 +    gazeta_pl.Config = gazeta_pl.Config || {};
 +    gazeta_pl.Config.Squid = {
 +        domain: 'GQOnEDUr8VUlxDfzolv2hQ',
 +        luid: '',
 +        dc: ''
 +    };
 +    //]]>
 +</script>
 +
 +<!-- paywallScriptModule 1.1 -->
 + +<!--13124793, [ /fix/modules/wyborcza/paywall/paywallScriptModule.jsp ], paywallScriptModule--> + +            
 +
 +
 +
 +
 +<!-- paywallUpperPlaceholder 1.1 -->
 + +<!--13124791, [ /fix/modules/wyborcza/paywall/paywallUpperPlaceholder.jsp ], paywallUpperPlaceholder--> + +             + + +<html> + <head></head> + <body> +  <section class="art_content" itemprop="articleSection"> +   <p class="art_paragraph">Choć Wrocław to piękne miasto, często pojawia się w polskiej czołówce tych najbardziej ogarniętych smogiem. Zdarza się, że i w światowym rankingu zajmuje wysokie lokaty. Wszystko zależy od tego, jaka akurat jest pogoda.</p> +   <p class="art_paragraph">Trójka zaniepokojonych wrocławian od dawna obserwuje wskazania dwóch oficjalnych stacji pomiarowych. I często aż strach jest wyjść z domu. Zresztą w nim też nie jest dużo lepiej.</p> +   <p class="art_paragraph">– Szkodliwe cząsteczki unoszące się w smogu są bardzo małe. Gdy ich stężenie jest wysokie, dostają się również do wewnątrz mieszkań. A to oznacza, że przed smogiem nie da się całkowicie ochronić, zamykając drzwi i okna – opowiada Adam Muszyński oraz Diana i Przemek Jaworscy, założyciele firmy produkującej innowacyjny element ubioru chroniący przed smogiem.</p> +   <p class="art_paragraph">Oni, jak i wielu ich znajomych, chcieli zadbać o zdrowie. Jednym ze sposobów na to jest noszenie specjalnej maseczki z filtrem. Do obrazka, na którym ludzie mają pozasłaniane białymi maseczkami twarze, przywykliśmy oglądając relacje z największych miast Azji, gdzie problem smogu jest szczególnie widoczny.</p> +  </section> + </body> +</html> + + +<!--13006101, [ /tpl/prod/content/article/modules/article_body_sst.jsp ], awdArticleBodySstModuleTomcat--> + +             + + + + + +<!-- percentageOfArticleBehindPaywallModule 1.1 --> + +<!--13270618, [ /fix/modules/wyborcza/percentageOfArticleBehindPaywallModule.jsp ], percentageOfArticleBehindPaywallModule--> + +            
 +
 +
 +
 +    <div class="mcBan" id="mcBan_1">
 +        <section class="padlock +padlock-article-title-align-center +locked +padlock-text-align-center" +data-cta="1pbox-2hd-5basic" data-cta-category="zajawka zamkniecia"> +<header class="padlock-header"> +<figure class="padlock-header-symbol"> +<svg class="icon"> +<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#diamond"></use> +</svg> +</figure> +<span class="padlock-header-msg">Artyku dostpny tylko w prenumeracie cyfrowej Wyborczej</span> +</header> +<div class="padlock-main"> +<div class="padlock-body-container"> +<article class="padlock-article"> +<h3 class="padlock-article-title">Wyprbuj cyfrow Wyborcz</h3> +<p class="padlock-article-lead">Nieograniczony dostp do serwisw informacyjnych, biznesowych, lokalnych i wszystkich magazynw Wyborczej</p> +</article> +<div class="padlock-action" data-utilize-child="true" data-utilize="x"> +<a class="padlock-action-url" href="http://prenumerata.wyborcza.pl/lp/0,145442.html?cta=1pbox-2hd-5basic"> +Wyprbuj teraz od 19,90 z +</a> +</div> +<footer class="padlock-footer"> +<ul class="padlock-footer-list"> +<li class="padlock-footer-list-login"> +<a class="pianoLoginBoxBtn" href="#"> +Zaloguj si +</a> +</li> +<li class="padlock-footer-list-contact"> +<a href="mailto:pomoc@wyborcza.pl"> +Kontakt +</a> +</li> +</ul> +</footer> +</div> +</div> +</section><!-- UZREditor --><!--22768337,aliasOf--><!-- htmEOF -->
 +    </div>
 +
 +
 +<!-- paywallBottomPlaceholder 1.1 -->
 + +<!--13124792, [ /fix/modules/wyborcza/paywall/paywallBottomPlaceholder.jsp ], paywallBottomPlaceholder--> + + +        </section> +     +     + + +<!-- articleModule v1.5 --> + +<!--9638918, [ /fix/modules/wyborcza/articleModule.jsp ], wyborczaArticleModule--> +
 +                        
 +
 +
 +
 +
 +<!-- partnerInfoModule -->
 + +<!--11917370, [ /fix/modules/wyborcza/portal/partnerInfoModule.jsp ], emptyBean--> +
 +                        
 +                        
 +
 +
 +
 +
 +<!-- ContentHolderExtractorModule v1.0.5 -->
 + +<!--13198008, [ /fix/modules/wyborcza/portal/contentHolderExtractorModule.jsp ], contentHolderExtractorModule--> +
 +                        
 +                        
 +
 +                    </article>
 +                    <div class="show-survey-quiz">
 +                        
 +                    </div>
 +                </div>
 +
 +        
 +            
 +            
 +        
 +
 +                <!--supertag-start -->
 +                
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +                <!-- supertag-end -->
 +
 +                <section class="post-article">
 +                    
 +
 +                    
 +                        <div class="post-article-content-holder column-54" data-position="54,1-5">
 +                            
 +                            
 +                             + + + +<!-- (~MODSTART : 7 :21407481) DFP 1.2 --> + + +     + +<div class="index zi_index_read_other"> +     +        <div class="head"> +             +                <h3> +                    Inne +                </h3> +             +             +        </div> +    <div class="body"> +        <ul> +             +             +             +			 +             +             +             +            <li class="entry   even article" > +                 +                 +                 +                 +                     +                         +                         +                         +                        <div class="imgw"> +                             +                                 +                                 +                                    <ul> +                                        <li  > +                                            <a href="http://wyborcza.biz/biznes/7,147743,24418098,inwestujemy-w-oczyszczacz-powietrza-podpowiadamy-jak-dobrac.html"> +                                                <img src="https://bi.im-g.pl/im/f9/49/17/z24418297II,Oczyszczacze-powietrza-pozwola-nam-zdrowo-oddychac.jpg"/> +                                            </a> +                                             +                                             +                                        </li> +                                    </ul> +                                 +                             +                        </div> +                         +                         +                         +                         +                         +                 +                 +                 +                 +                 +                 +                 +                 +                     +                         +                         +                         +                         +                         +                         +                         +                         +                         +                 +                 +                 +                    <span class="base"><a  href="http://wyborcza.biz/biznes/0,147743.html">Konsument i zakupy</a> +                    </span> +                 +                 +                 +                 +                 +                     +                         +                         +                            <h4> +                                <a title="Inwestujemy w oczyszczacz powietrza. Podpowiadamy, jak dobra urzdzenie i ile to kosztuje" href="http://wyborcza.biz/biznes/7,147743,24418098,inwestujemy-w-oczyszczacz-powietrza-podpowiadamy-jak-dobrac.html">Inwestujemy w oczyszczacz powietrza. Podpowiadamy, jak dobra urzdzenie i ile to kosztuje</a> +                            </h4> +                         +                         +                         +                         +                         +                         +                         +                 +                 +                 +                 +                 +                 +                 +                 +            </li> + +             +             + +             +             +             +			 +             +             +             +            <li class="entry   odd article" > +                 +                 +                 +                 +                     +                         +                         +                         +                        <div class="imgw"> +                             +                                 +                                 +                                    <ul> +                                        <li  > +                                            <a href="http://wyborcza.biz/biznes/7,147743,24417936,pomysl-na-biznes-chusta-ktora-chroni-przed-smogiem.html"> +                                                <img src="https://bi.im-g.pl/im/f7/49/17/z24418295II,Prace-nad-projektem-chusty-antysmogowej-rozpoczely.jpg"/> +                                            </a> +                                             +                                             +                                        </li> +                                    </ul> +                                 +                             +                        </div> +                         +                         +                         +                         +                         +                 +                 +                 +                 +                 +                 +                 +                 +                     +                         +                         +                         +                         +                         +                         +                         +                         +                         +                 +                 +                 +                    <span class="base"><a  href="http://wyborcza.biz/biznes/0,147743.html">Konsument i zakupy</a> +                    </span> +                 +                 +                 +                 +                 +                     +                         +                         +                            <h4> +                                <a title="Pomys na biznes: chusta, ktra chroni przed smogiem" href="http://wyborcza.biz/biznes/7,147743,24417936,pomysl-na-biznes-chusta-ktora-chroni-przed-smogiem.html">Pomys na biznes: chusta, ktra chroni przed smogiem</a> +                            </h4> +                         +                         +                         +                         +                         +                         +                         +                 +                 +                 +                 +                 +                 +                 +                 +            </li> + +             +             + +             +             +             +			 +             +             +             +            <li class="entry   even article" > +                 +                 +                 +                 +                     +                         +                         +                         +                        <div class="imgw"> +                             +                                 +                                 +                                    <ul> +                                        <li  > +                                            <a href="http://wyborcza.biz/biznes/7,147743,24314790,polskie-kasjerki-zaczynaja-sie-bac-kiedy-straca-prace-przez.html"> +                                                <img src="https://bi.im-g.pl/im/3c/32/17/z24325436II,Kasa-samoobslugowa-w-drogerii-Rossmann-.jpg"/> +                                            </a> +                                             +                                             +                                        </li> +                                    </ul> +                                 +                             +                        </div> +                         +                         +                         +                         +                         +                 +                 +                 +                 +                 +                 +                 +                 +                     +                         +                         +                         +                         +                         +                         +                         +                         +                         +                 +                 +                 +                    <span class="base"><a  href="http://wyborcza.biz/biznes/0,147743.html">Konsument i zakupy</a> +                    </span> +                 +                 +                 +                 +                 +                     +                         +                         +                            <h4> +                                <a title="Kasjerki i kasjerzy si boj. Kiedy strac prac przez automaty?" href="http://wyborcza.biz/biznes/7,147743,24314790,polskie-kasjerki-zaczynaja-sie-bac-kiedy-straca-prace-przez.html">Kasjerki i kasjerzy si boj. Kiedy strac prac przez automaty?</a> +                            </h4> +                         +                         +                         +                         +                         +                         +                         +                 +                 +                 +                 +                 +                 +                 +                 +            </li> + +             +             + +             +             +             +			 +             +             +             +            <li class="entry   odd article" > +                 +                 +                 +                 +                     +                         +                         +                         +                        <div class="imgw"> +                             +                                 +                                 +                                    <ul> +                                        <li  > +                                            <a href="http://wyborcza.biz/biznes/7,147743,24323989,zywnosc-gaz-prad-paliwa-ceny-pietruszki-moga-byc-rekordowe.html"> +                                                <img src="https://bi.im-g.pl/im/e3/32/17/z24324067II,Warzywa.jpg"/> +                                            </a> +                                             +                                             +                                        </li> +                                    </ul> +                                 +                             +                        </div> +                         +                         +                         +                         +                         +                 +                 +                 +                 +                 +                 +                 +                 +                     +                         +                         +                         +                         +                         +                         +                         +                         +                         +                 +                 +                 +                    <span class="base"><a  href="http://wyborcza.biz/biznes/0,147743.html">Konsument i zakupy</a> +                    </span> +                 +                 +                 +                 +                 +                     +                         +                         +                            <h4> +                                <a title="ywno, gaz, prd, paliwa... Ceny pietruszki mog by rekordowe. Co jeszcze zdroeje w 2019? A co stanieje?" href="http://wyborcza.biz/biznes/7,147743,24323989,zywnosc-gaz-prad-paliwa-ceny-pietruszki-moga-byc-rekordowe.html">ywno, gaz, prd, paliwa... Ceny pietruszki mog by rekordowe. Co jeszcze zdroeje w 2019? A co stanieje?</a> +                            </h4> +                         +                         +                         +                         +                         +                         +                         +                 +                 +                 +                 +                 +                 +                 +                 +            </li> + +             +             + +             +             +             +			 +             +             +             +            <li class="entry   even article" > +                 +                 +                 +                 +                     +                         +                         +                         +                        <div class="imgw"> +                             +                                 +                                 +                                    <ul> +                                        <li  > +                                            <a href="http://wyborcza.biz/biznes/7,147743,24303027,eksperyment-sklepowy-z-slodyczy-rezygnowac-nie-lubimy-przy.html"> +                                                <img src="https://bi.im-g.pl/im/70/17/17/z24212592II.jpg"/> +                                            </a> +                                             +                                             +                                        </li> +                                    </ul> +                                 +                             +                        </div> +                         +                         +                         +                         +                         +                 +                 +                 +                 +                 +                 +                 +                 +                     +                         +                         +                         +                         +                         +                         +                         +                         +                         +                 +                 +                 +                    <span class="base"><a  href="http://wyborcza.biz/biznes/0,147743.html">Konsument i zakupy</a> +                    </span> +                 +                 +                 +                 +                 +                     +                         +                         +                            <h4> +                                <a title="Ze sodyczy rezygnowa nie lubimy. Przy rybie robimy si asertywni. Na zakupach nie zawsze kierujemy si cen" href="http://wyborcza.biz/biznes/7,147743,24303027,eksperyment-sklepowy-z-slodyczy-rezygnowac-nie-lubimy-przy.html">Ze sodyczy rezygnowa nie lubimy. Przy rybie robimy si asertywni. Na zakupach nie zawsze kierujemy si cen</a> +                            </h4> +                         +                         +                         +                         +                         +                         +                         +                 +                 +                 +                 +                 +                 +                 +                 +            </li> + +             +             + +             +             +             +			 +             +             +             +            <li class="entry   odd article" > +                 +                 +                 +                 +                     +                         +                         +                         +                        <div class="imgw"> +                             +                                 +                                 +                                    <ul> +                                        <li  > +                                            <a href="http://wyborcza.biz/biznes/7,147743,24293124,idzie-boom-kawowy-bedzie-taniej-i-zdrowiej.html"> +                                                <img src="https://bi.im-g.pl/im/bf/0a/17/z24161983II.jpg"/> +                                            </a> +                                             +                                             +                                        </li> +                                    </ul> +                                 +                             +                        </div> +                         +                         +                         +                         +                         +                 +                 +                 +                 +                 +                 +                 +                 +                     +                         +                         +                         +                         +                         +                         +                         +                         +                         +                 +                 +                 +                    <span class="base"><a  href="http://wyborcza.biz/biznes/0,147743.html">Konsument i zakupy</a> +                    </span> +                 +                 +                 +                 +                 +                     +                         +                         +                            <h4> +                                <a title="Idzie boom kawowy. Bdzie taniej i zdrowiej" href="http://wyborcza.biz/biznes/7,147743,24293124,idzie-boom-kawowy-bedzie-taniej-i-zdrowiej.html">Idzie boom kawowy. Bdzie taniej i zdrowiej</a> +                            </h4> +                         +                         +                         +                         +                         +                         +                         +                 +                 +                 +                 +                 +                 +                 +                 +            </li> + +             +             + +             + +        </ul> +    </div> +    <div class="footer"> +         +             +             +    </div> +</div> +<!-- (~MODEND:7:21407481 s43a:1,313,063) --> + +<!--21407481, [ /tpl/prod/universalIndex/universalIndex.jsp ], universalIndexBean--> +
 +                            
 +                            
 +                        </div>
 +                        <section class="post-article-related">
 +                            
 +
 +
 +
 +
 +
 +
 +
 +<!-- related debug: RelatedResultDto{relatedItems=[RelatedItem [xx=24414148, title=Duo smogu, mao alarmw. Polska "mistrzem" rakotwrczych wyzieww, url=http://wyborcza.pl/7,155287,24414148,europejski-raport-o-smogu-polska-walczy-za-slabo.html, photoUrl=https://bi.im-g.pl/im/3e/29/17/z24289086D.jpg, photoTitle=null], RelatedItem [xx=24376685, title=Miliony Polakw mog wreszcie odliczy od podatku panele soneczne. I... dalej dorzuca do kopciuchw, url=http://wyborcza.pl/7,155287,24376685,miliony-polakow-moga-wreszcie-odliczyc-od-podatku-panele-sloneczne.html, photoUrl=https://bi.im-g.pl/im/24/3f/17/z24376868D,Panele-fotowoltaiczne.jpg, photoTitle=Panele fotowoltaiczne], RelatedItem [xx=24349296, title=Kolejny milion uywanych aut z importu. Dodatkowa dostawa smogu z Zachodu, url=http://wyborcza.pl/7,155287,24349296,kolejny-milion-uzywanych-aut-z-importu-dodatkowa-dostawa-smogu.html, photoUrl=https://bi.im-g.pl/im/39/39/17/z24351545D,Uzywane-auta-z-Niemiec---w-drodze-do-polskiego-nab.jpg, photoTitle=Uywane auta z Niemiec - w drodze do polskiego nabywcy. A4, 29 stycznia 2017], RelatedItem [xx=24302580, title=Jaki oczyszczacz powietrza kupi do domu?, url=http://wyborcza.biz/pieniadzeekstra/7,134263,24302580,jaki-oczyszczacz-powietrza-kupic-do-domu.html, photoUrl=https://bi.im-g.pl/im/1f/2d/17/z24302879D,Oczyszczacz-powietrza.jpg, photoTitle=Oczyszczacz powietrza]], putBanPosition=2, articleAmount=4, itemsNoWithPutban=4} -->
 +
 +
 +    <div class="gazeta_article_related_new">
 +        <div class="rel_head" >
 +            Zobacz take
 +        </div>
 +
 +        
 +            
 +            
 +                                                                         
 +            
 +        
 +        <ul>
 +               
 +            
 +                <li class="fLeft ">
 +                    
 +                        
 +                        
 +                                  
 +                            <a href="http://wyborcza.pl/7,155287,24414148,europejski-raport-o-smogu-polska-walczy-za-slabo.html" title="Duo smogu, mao alarmw. Polska "mistrzem" rakotwrczych wyzieww">
 +                                <img src="https://bi.im-g.pl/im/3e/29/17/z24289086II.jpg" alt=""/>   
 +                            </a>
 +                            <a class="t" href="http://wyborcza.pl/7,155287,24414148,europejski-raport-o-smogu-polska-walczy-za-slabo.html" title="Duo smogu, mao alarmw. Polska "mistrzem" rakotwrczych wyzieww" >Duo smogu, mao alarmw. Polska "mistrzem" rakotwrczych wyzieww</a>
 +                             
 +                        
 +                    
 +                </li>
 +            
 +                <li class="">
 +                    
 +                        
 +                        
 +                                  
 +                            <a href="http://wyborcza.pl/7,155287,24376685,miliony-polakow-moga-wreszcie-odliczyc-od-podatku-panele-sloneczne.html" title="Miliony Polakw mog wreszcie odliczy od podatku panele soneczne. I... dalej dorzuca do kopciuchw">
 +                                <img src="https://bi.im-g.pl/im/24/3f/17/z24376868II,Panele-fotowoltaiczne.jpg" alt="Panele fotowoltaiczne"/>   
 +                            </a>
 +                            <a class="t" href="http://wyborcza.pl/7,155287,24376685,miliony-polakow-moga-wreszcie-odliczyc-od-podatku-panele-sloneczne.html" title="Miliony Polakw mog wreszcie odliczy od podatku panele soneczne. I... dalej dorzuca do kopciuchw" >Miliony Polakw mog wreszcie odliczy od podatku panele soneczne. I... dalej dorzuca do kopciuchw</a>
 +                             
 +                        
 +                    
 +                </li>
 +            
 +                <li class="">
 +                    
 +                        
 +
 +                            
 +                                
 +                                
 +                                                                       
 +                                
 +                            
 +
 +							
 +								
 +									<script src='https://www.googletagservices.com/tag/js/gpt.js'>
 +									    var _YB = _YB || {
 +									        ab: function() {
 +									            return (Math.random() >= 0.1 ? 'b' : 'a' + Math.floor(Math.random() * 10));
 +									        }
 +									    };
 +									    var _yt = new Date(), yb_th = _yt.getUTCHours() - 8, yb_tm = _yt.getUTCMinutes(), yb_wd = _yt.getUTCDay();
 +									    if (yb_th < 0) {
 +									        yb_th = 24 + yb_th;
 +									        yb_wd -= 1;
 +									    };
 +									    if (yb_wd < 0) {
 +									        yb_wd = 7 + yb_wd
 +									    };
 +									    googletag.pubads().definePassback('/52555387/wyborcza.pl_native_desktop', ['fluid']).setTargeting('yb_ab', _YB.ab()).setTargeting('yb_ff', String(Math.round(Math.random()))).setTargeting('yb_th', yb_th.toString()).setTargeting('yb_tm', yb_tm.toString()).setTargeting('yb_wd', yb_wd.toString()).display();
 +									</script>
 +								
 +								
 +							
 +
 +						
 +                        
 +                    
 +                </li>
 +            
 +                <li class="">
 +                    
 +                        
 +                        
 +                                  
 +                            <a href="http://wyborcza.pl/7,155287,24349296,kolejny-milion-uzywanych-aut-z-importu-dodatkowa-dostawa-smogu.html" title="Kolejny milion uywanych aut z importu. Dodatkowa dostawa smogu z Zachodu">
 +                                <img src="https://bi.im-g.pl/im/39/39/17/z24351545II,Uzywane-auta-z-Niemiec---w-drodze-do-polskiego-nab.jpg" alt="Uywane auta z Niemiec - w drodze do polskiego nabywcy. A4, 29 stycznia 2017"/>   
 +                            </a>
 +                            <a class="t" href="http://wyborcza.pl/7,155287,24349296,kolejny-milion-uzywanych-aut-z-importu-dodatkowa-dostawa-smogu.html" title="Kolejny milion uywanych aut z importu. Dodatkowa dostawa smogu z Zachodu" >Kolejny milion uywanych aut z importu. Dodatkowa dostawa smogu z Zachodu</a>
 +                             
 +                        
 +                    
 +                </li>
 +            
 +
 +        </ul>
 +    </div>
 +
 +
 +
 +<!-- /relatedModule v 2.0 -->
 +
 + +<!--9638939, [ /fix/modules/wyborcza/relatedModule_dfp.jsp ], relatedArticlesModule--> +
 +                        </section>
 +                        <div class="post-article-content-holder column-48" data-position="48,1-5 12-15">
 +                            
 +                            
 +                            
 +                            
 +                            
 +                            
 +                            
 +                            
 +                            
 +                        </div>
 +                    
 +
 +                    
 +
 +
 +
 +	 <div id="tags" class="tags">
 +		<span class="tags-label">Wicej na ten temat:</span>
 +		
 +			<a href="/biznes/0,104259.html?tag=smog" title="smog" class="tag-anchor"><span class="tag-label">smog</span></a>,
 + 		
 +			<a href="/biznes/0,104259.html?tag=w%EAgiel" title="wgiel" class="tag-anchor"><span class="tag-label">wgiel</span></a>,
 + 		
 +			<a href="/biznes/0,104259.html?tag=jako%B6%E6+powietrza" title="jako powietrza" class="tag-anchor"><span class="tag-label">jako powietrza</span></a>,
 + 		
 +			<a href="/biznes/0,104259.html?tag=zanieczyszczenie+powietrza" title="zanieczyszczenie powietrza" class="tag-anchor"><span class="tag-label">zanieczyszczenie powietrza</span></a>
 + 		
 +	</div>
 +
 +
 +<!-- universalTags v1.0 -->
 + +<!--410352220, [ /fix/modules/wyborcza/universalTags.jsp ], seoTagController--> +
 +                </section>
 +
 +                <div class="social">
 +                    <div class="social-bar">
 +                        
 +
 +
 +
 +
 +
 +
 +	<div class="socialBar">
 +		<ul>
 +            <li class="readLater">
 +					<a href="#" title="Dodaj do schowka">
 +                                            <figure class="badge-ico">
 +                                                <svg class="badge-symbol">
 +                                                        <use xlink:href="#read-later"></use>
 +                                                </svg>
 +                                            </figure>
 +                                        </a>
 +                                        <span class="sharesCount"></span>
 +				</li>
 +
 +			
 +				<li class="fbShare">
 +					<a href="#" title="Udostpnij na Facebooku">
 +                                            <figure class="badge-ico">
 +                                                <svg class="badge-symbol">
 +                                                        <use xlink:href="#face-v2"></use>
 +                                                </svg>
 +                                            </figure>
 +                                        </a>
 +                                        <span class="sharesCount"></span>
 +				</li>
 +			
 +
 +			
 +				<li class="twitter">
 +					<a href="#" title="Udostpnij na Twitterze">
 +                                            <figure class="badge-ico">
 +                                                <svg class="badge-symbol">
 +                                                        <use xlink:href="#twitter-v2"></use>
 +                                                </svg>
 +                                            </figure>
 +                                        </a>
 +                                        <span class="tweetsCount"></span>
 +				</li>
 +			
 +
 +			
 +				
 +				<li class="email">
 +					<a href="http://gazeta.hit.gemius.pl/hitredir/id=nF6a2AyFf_IJ3woQ3DRcKcewPxLw1u7SUeW3YDM9WRb.P7/stparam=wkionrdgis/url=http://wyborcza.biz/2029030,75247.html?b=1&x=24417936&d=0&utm_source=recommend&utm_medium=email&utm_campaign=recommend&home=http://wyborcza.biz/biznes/7,147743,24417936,pomysl-na-biznes-chusta-ktora-chroni-przed-smogiem.html?disableRedirects=true" title="Udostpnij e-mailem">
 +                                            <figure class="badge-ico">
 +                                                <svg class="badge-symbol">
 +                                                        <use xlink:href="#mail-v2"></use>
 +                                                </svg>
 +                                            </figure>
 +                                        </a>
 +				</li>
 +			
 +
 +			
 +				<li class="comments">
 +                                    <a href="#" title="Zobacz komentarze">
 +                                        <figure class="badge-ico">
 +                                            <svg class="badge-symbol">
 +                                                    <use xlink:href="#comments-v2"></use>
 +                                            </svg>
 +                                        </figure>
 +                                    </a>
 +                                        <span class="commentsCount"></span>
 +				</li>
 +			
 +
 +		</ul>
 +	</div>
 +
 +
 +<!-- socialBar v1.1 -->
 + +<!--9638922, [ /fix/modules/wyborcza/socialBar.jsp ], toolsSocialMediaPojoBean--> +
 +                    </div>
 +                    <div class="comments-container">
 +                        
 +
 +
 +
 +
 +
 +
 +<!-- opinions20 -->
 +<!-- serwis: 2 -->
 +<!-- role:  -->
 +
 +
 +
 +
 +
 +
 +   
 +
 +
 +<script>
 +    wyborcza_pl.commentsUserData = Object.freeze((function () {
 +        var opts = {
 +            roles: {
 +                admin: false,
 +                editor: false
 +            },
 +            userNick: "",
 +            loginChannel: '',
 +            userLink: '',
 +            wyborcza_pl_service_id: 2,
 +            voting: true,
 +            writing: false,
 +            commentedObjectId: 24417936
 +        };
 +
 +        function getParam(paramName) {
 +            return opts[paramName];
 +        }
 +
 +        function getRole(roleName) {
 +            return opts['roles'][roleName];
 +        }
 +
 +        return {
 +            getParam: getParam,
 +            isRole: getRole
 +        };
 +
 +    })());
 +</script>
 +
 +
 +
 +    <!-- komentarze ON -->
 +    <!-- test session: '' / '' : '' : '' -->
 +
 +    
 +        <section class="comments">
 +    
 +    
 +
 +        <header class="oHead">
 +            <div class="oCntInf">
 +                <span class="ocntHead">Komentarze</span>
 +                <div class="oCntWrap">
 +                    <svg class="oCntBg">
 +                        <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#comment"></use>
 +                    </svg>
 +                    <span id="oCnt"></span>
 +                </div>
 +            </div>
 +
 +            <!-- komentarze_pisanie: 'false' -->
 +            <div class="oFormBox">
 +                <form action="#" method="post" class="oForm">
 +                    <div class="row rTop">
 +
 +                    
 +
 +                    
 +                        <div class="subscribeToWriteComment">Chcesz doczy do dyskusji? <a href="//wyborcza.pl/komentarzeoferta">Zosta naszym prenumeratorem</a></div>
 +                    
 +
 +                    </div>
 +
 +                    <div class="row rBtm">
 +
 +                        
 +                            <div class="cLogin">
 +                                <span class="cLoginLink">Zaloguj si</span>
 +                                <div class="logOpts">
 +
 +                                    <a data-action-login="wyborcza" id="oLoginWyborcza" title="Zaloguj uywajc konta Wyborcza">
 +                                        <svg xmlns="http://www.w3.org/2000/svg" class="logOptsWybprcza" viewBox="0 0 28 28">
 +                                            <defs>
 +                                                <style>
 +                                                    #wyborcza_PL_rect1 { fill: #fff; } #wyborcza_PL_rect2, #wyborcza_PL_polygon1 { fill: #a1a1a1; } #wyborcza_PL:hover #wyborcza_PL_polygon1 { fill: #000; } #wyborcza_PL:hover #wyborcza_PL_rect1 { fill: #f1f1f1; } #wyborcza_PL:hover #wyborcza_PL_rect2 { fill: #df012d; }
 +                                                </style>
 +                                            </defs>
 +                                            <g id="wyborcza_PL">
 +                                                <rect id="wyborcza_PL_rect1" width="28" height="28"/>
 +                                                <rect id="wyborcza_PL_rect2" width="12.22" height="18.84" x="3.56" y="4.58"/>
 +                                                <polygon id="wyborcza_PL_polygon1" points="26.73 10.38 25.81 10.38 24.16 16.62 24.03 16.61 22.14 11.2 23.26 10.54 23.26 10.38 18.55 10.38 18.55 10.54 19.29 11.21 22.25 19.53 23.95 19.53 25.31 14.44 25.45 14.44 27.25 19.53 28 19.53 28 14.03 26.73 10.38"/>
 +                                            </g>
 +                                        </svg>
 +                                    </a>
 +
 +                                    <a data-action-login="facebook" id="oLoginFacebook" title="Zaloguj uywajc Facebooka">
 +                                        <svg xmlns="http://www.w3.org/2000/svg" class="logOptsFacebook" viewBox="0 0 28 28">
 +                                            <defs>
 +                                                <style>
 +                                                    #Facebook_Ico #Facebook_Ico_path1 { fill: #fff; fill-rule: evenodd; } #Facebook_Ico:hover #Facebook_Ico_path1 { fill: #3664a2; fill-rule: evenodd; } #Facebook_Ico #Facebook_Ico_rect1 { fill: #a1a1a1; } #Facebook_Ico:hover #Facebook_Ico_rect1 { fill: #fff; }
 +                                                </style>
 +                                            </defs>
 +                                            <g id="Facebook_Ico">
 +                                                <rect id="Facebook_Ico_rect1" width="28" height="28"/>
 +                                                <path id="Facebook_Ico_path1" d="M-9.28-4.3v28h28v-28h-28ZM9,9.28H5.73v9.42H2.21V9.27H-0.29V7.14H2.22V4.41c0-4,4.42-3.69,4.42-3.69h2.6V3.29H7.14A1.17,1.17,0,0,0,5.73,4.35V7.14h4Z" transform="translate(9.28 4.3)"/>
 +                                            </g>
 +                                        </svg>
 +                                    </a>
 +
 +                                    <a data-action-login="google" id="oLoginGoogle" title="Zaloguj uywajc Google+">
 +                                        <svg xmlns="http://www.w3.org/2000/svg" class="logOptsFacebook" viewBox="0 0 28 28">
 +                                            <defs>
 +                                                <style>
 +                                                    #GooglePlus_Ico #GooglePlus_Ico_path1 { fill: #fff; fill-rule: evenodd; } #GooglePlus_Ico:hover #GooglePlus_Ico_path1 { fill-rule: evenodd; fill: #d34836; } #GooglePlus_Ico:hover #GooglePlus_Ico_rect1 { fill: #fff; } #GooglePlus_Ico #GooglePlus_Ico_rect1 { fill: #a1a1a1; }
 +                                                </style>
 +                                            </defs>
 +                                            <g id="GooglePlus_Ico">
 +                                                <rect id="GooglePlus_Ico_rect1" width="28" height="28"/>
 +                                                <path id="GooglePlus_Ico_path1" d="M0,0V28H28V0H0ZM10.36,20A6.18,6.18,0,0,1,4,14a6.18,6.18,0,0,1,6.36-6,6.31,6.31,0,0,1,4.26,1.57L12.9,11.14a3.7,3.7,0,0,0-2.54-.93A3.87,3.87,0,0,0,6.42,14a3.87,3.87,0,0,0,3.95,3.79A3.39,3.39,0,0,0,14,15.2H10.36V13.14h6a5.23,5.23,0,0,1,.1,1C16.47,17.57,14,20,10.36,20ZM24,14.86H22.18v1.71H20.36V14.86H18.55V13.14h1.82V11.43h1.82v1.71H24v1.71Z"/>
 +                                            </g>
 +                                        </svg>
 +                                    </a>
 +
 +                                </div>
 +
 +                                
 +
 +                            </div>
 +                        
 +
 +                        
 +
 +                        
 +
 +                        <div class="cSubmit">
 +                            <button id="oFormSubmit" class="sendCommentButton" disabled="disabled" name="submit">Skomentuj</button>
 +                        </div>
 +                    </div>
 +                </form>
 +            </div>
 +
 +            <!-- komentarze_sortowanie: PopularityDesc -->
 +            <div class="oSortOpts" data-sort="Popularity-Desc">
 +                <a data-action="sortComments" data-sort-by="Time">Najnowsze</a>
 +                <a data-action="sortComments" data-sort-by="Popularity">Popularne</a>
 +            </div>
 +        </header><!-- /header -->
 +
 +
 +        
 +
 +        
 +
 +        <!-- komentarze_liczba_pod_art: 8 -->
 +        
 +            <section class="oBody">
 +                
 +
 +                    <!-- komentarz -->
 +
 +                    <div class="cRow cResHidden flag_PUBLISHED" id="opinion101252015" itemscope
 +                         itemtype="http://schema.org/Comment" itemid="#opinion:101252015">
 +                        <div class="cHead">
 +                            
 +                            <span itemprop="author" itemscope itemtype="http://schema.org/Person"><span class="cName" itemprop="name">
 +                                 
 +                                     
 +                                     
 +                                         sceptyczny_debunk
 +                                     
 +                                 
 +                            </span></span>
 +                            
 +                            <span class="cDate" itemprop="datePublished" content="2019-01-31">31.01.2019, 16:16</span>
 +                            
 +                            
 +                        </div>
 +                        <div class="cBody">
 +                            
 +                                
 +                            
 +                            <span itemprop="text">W Azji maski nosi si nie z powodu smogu, tylko SARS, ptasiej grypy i z powodu przepisw sanitarnych, ktre nakazuj nosi maski w miejscach publicznych osobom kaszlcym czy kichajcym. Z tego samego powodu w wielu miejscach publicznych s montowane kamery mierzce temperatur ciaa przechodniw, np. w metrze w Hongkongu czy w Tajpej. Jak si patrzy na polski autobus w styczniu peen zasmarkanych, kichajcych i kaszlcych ludzi, ktrzy nawet doni nie zasaniaj ust, to trzeba si zastanowi, jakim cudem ten nard jeszcze nie wymar.</span></div>
 +                        <div class="cFt">
 +                            
 +                                <!-- komentarze_ocenianie ON -->
 +                                <div class="cAlreadyVoted"><span class="cVotedInfo">ju oceniae(a)</span></div>
 +                                <div class="cVoteUp">
 +                                    <a data-action="vote-addPlus" data-post-id="101252015">
 +                                        <svg class="voteUpIcon">
 +                                            <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#vote-up"></use>
 +                                        </svg>
 +                                    </a>
 +                                    <span itemprop="upvoteCount">9</span>
 +                                </div>
 +
 +                                <div class="cVoteDown">
 +                                    <a data-action="vote-addMinus" data-post-id="101252015">
 +                                        <svg class="voteDownIcon">
 +                                            <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#vote-down"></use>
 +                                        </svg>
 +                                    </a>
 +                                    <span itemprop="downvoteCount">1</span>
 +                                </div>
 +                            
 +                            <div class="cVoteSpam">
 +                                <a title="Zgo do moderacji" data-action="trash-vote" data-post-id="101252015">
 +                                    <svg class="voteSpamIcon">
 +                                        <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#spam"></use>
 +                                    </svg>
 +                                </a>
 +                            </div>
 +                            <div class="cResWrite">
 +                                <a data-child-for="101252015" data-reply-to="sceptyczny_debunk">
 +                                    <svg class="resWriteIcon">
 +                                        <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#reply"></use>
 +                                    </svg>
 +                                    Odpowiedz</a>
 +                            </div>
 +
 +                            
 +                                <div class="cResShow">
 +                                    <a data-res-counter="1" data-state="show">
 +                                        <svg class="resShowIcon">
 +                                            <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#comment"></use>
 +                                        </svg>
 +                                        
 +                                            
 +                                            
 +                                                 Poka odpowiedzi (1)
 +                                            
 +                                        
 +                                    </a>
 +                                </div>
 +                            
 +                        </div>
 +
 +
 +                        
 +                            <!-- Odpowiedzi na komentarz -->
 +                            
 +                                <!-- komentarz zagniezdzony -->
 +                                <div class="cRow cResComment flag_PUBLISHED" id="opinion101252118"
 +                                     itemscope itemtype="http://schema.org/Comment" itemid="#opinion:101252118">
 +                                    <div class="cHead">
 +                                        
 +                                        <span itemprop="author" itemscope itemtype="http://schema.org/Person"><span class="cName" itemprop="name">
 +                                            
 +                                            
 +                                            
 +                                                lizabet
 +                                            
 +                                        </span></span>
 +                                        
 +                                        <span class="cDate" itemprop="datePublished"
 +                                              content="2019-01-31">31.01.2019, 16:39</span>
 +                                        
 +                                        
 +                                    </div>
 +                                    <div class="cBody">
 +                                        
 +                                            
 +                                        
 +                                        <span itemprop="text">@sceptyczny_debunk<br/>Czsto o tym myl. Dziki za te celne uwagi.</span></div>
 +                                    <div class="cFt">
 +                                        
 +                                            <!-- komentarze_ocenianie ON -->
 +                                            <div class="cAlreadyVoted"><span class="cVotedInfo">ju oceniae(a)</span></div>
 +                                            <div class="cVoteUp">
 +                                                <a data-action="vote-addPlus" data-post-id="101252118">
 +                                                    <svg class="voteUpIcon">
 +                                                        <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#vote-up"></use>
 +                                                    </svg>
 +                                                </a>
 +                                                <span itemprop="upvoteCount">1</span>
 +                                            </div>
 +
 +                                            <div class="cVoteDown">
 +                                                <a data-action="vote-addMinus" data-post-id="101252118">
 +                                                    <svg class="voteDownIcon">
 +                                                        <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#vote-down"></use>
 +                                                    </svg>
 +                                                </a>
 +                                                <span itemprop="downvoteCount">0</span>
 +                                            </div>
 +                                        
 +                                        <div class="cVoteSpam">
 +                                            <a title="Zgo do moderacji" data-action="trash-vote" data-post-id="101252118">
 +                                                <svg class="voteSpamIcon">
 +                                                    <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#spam"></use>
 +                                                </svg>
 +                                            </a>
 +                                        </div>
 +                                        <div class="cResWrite">
 +                                            <a data-child-for="101252015" data-reply-to="lizabet">
 +                                                <svg class="resWriteIcon">
 +                                                    <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#reply"></use>
 +                                                </svg>
 +                                                Odpowiedz</a>
 +                                        </div>
 +                                    </div>
 +                                </div>
 +                                <!-- / komentarz zagniezdzony -->
 +                            
 +
 +                        
 +
 +
 +                    </div>
 +                    <!-- / komentarz -->
 +
 +                
 +
 +                    <!-- komentarz -->
 +
 +                    <div class="cRow cResHidden flag_PUBLISHED" id="opinion101252057" itemscope
 +                         itemtype="http://schema.org/Comment" itemid="#opinion:101252057">
 +                        <div class="cHead">
 +                            
 +                            <span itemprop="author" itemscope itemtype="http://schema.org/Person"><span class="cName" itemprop="name">
 +                                 
 +                                     
 +                                     
 +                                         tegonielubie
 +                                     
 +                                 
 +                            </span></span>
 +                            
 +                            <span class="cDate" itemprop="datePublished" content="2019-01-31">31.01.2019, 16:25</span>
 +                            
 +                            
 +                        </div>
 +                        <div class="cBody">
 +                            
 +                                
 +                            
 +                            <span itemprop="text">cena - 65 euro</span></div>
 +                        <div class="cFt">
 +                            
 +                                <!-- komentarze_ocenianie ON -->
 +                                <div class="cAlreadyVoted"><span class="cVotedInfo">ju oceniae(a)</span></div>
 +                                <div class="cVoteUp">
 +                                    <a data-action="vote-addPlus" data-post-id="101252057">
 +                                        <svg class="voteUpIcon">
 +                                            <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#vote-up"></use>
 +                                        </svg>
 +                                    </a>
 +                                    <span itemprop="upvoteCount">2</span>
 +                                </div>
 +
 +                                <div class="cVoteDown">
 +                                    <a data-action="vote-addMinus" data-post-id="101252057">
 +                                        <svg class="voteDownIcon">
 +                                            <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#vote-down"></use>
 +                                        </svg>
 +                                    </a>
 +                                    <span itemprop="downvoteCount">1</span>
 +                                </div>
 +                            
 +                            <div class="cVoteSpam">
 +                                <a title="Zgo do moderacji" data-action="trash-vote" data-post-id="101252057">
 +                                    <svg class="voteSpamIcon">
 +                                        <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#spam"></use>
 +                                    </svg>
 +                                </a>
 +                            </div>
 +                            <div class="cResWrite">
 +                                <a data-child-for="101252057" data-reply-to="tegonielubie">
 +                                    <svg class="resWriteIcon">
 +                                        <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#reply"></use>
 +                                    </svg>
 +                                    Odpowiedz</a>
 +                            </div>
 +
 +                            
 +                        </div>
 +
 +
 +                        
 +
 +
 +                    </div>
 +                    <!-- / komentarz -->
 +
 +                
 +
 +                    <!-- komentarz -->
 +
 +                    <div class="cRow cResHidden flag_PUBLISHED" id="opinion101251975" itemscope
 +                         itemtype="http://schema.org/Comment" itemid="#opinion:101251975">
 +                        <div class="cHead">
 +                            
 +                            <span itemprop="author" itemscope itemtype="http://schema.org/Person"><span class="cName" itemprop="name">
 +                                 
 +                                     
 +                                     
 +                                         baza
 +                                     
 +                                 
 +                            </span></span>
 +                            
 +                            <span class="cDate" itemprop="datePublished" content="2019-01-31">31.01.2019, 16:06</span>
 +                            
 +                            
 +                        </div>
 +                        <div class="cBody">
 +                            
 +                                
 +                            
 +                            <span itemprop="text">Zwyky wentylator nakryj filtrem wglowym i w domu oczyci powietrze.</span></div>
 +                        <div class="cFt">
 +                            
 +                                <!-- komentarze_ocenianie ON -->
 +                                <div class="cAlreadyVoted"><span class="cVotedInfo">ju oceniae(a)</span></div>
 +                                <div class="cVoteUp">
 +                                    <a data-action="vote-addPlus" data-post-id="101251975">
 +                                        <svg class="voteUpIcon">
 +                                            <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#vote-up"></use>
 +                                        </svg>
 +                                    </a>
 +                                    <span itemprop="upvoteCount">1</span>
 +                                </div>
 +
 +                                <div class="cVoteDown">
 +                                    <a data-action="vote-addMinus" data-post-id="101251975">
 +                                        <svg class="voteDownIcon">
 +                                            <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#vote-down"></use>
 +                                        </svg>
 +                                    </a>
 +                                    <span itemprop="downvoteCount">1</span>
 +                                </div>
 +                            
 +                            <div class="cVoteSpam">
 +                                <a title="Zgo do moderacji" data-action="trash-vote" data-post-id="101251975">
 +                                    <svg class="voteSpamIcon">
 +                                        <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#spam"></use>
 +                                    </svg>
 +                                </a>
 +                            </div>
 +                            <div class="cResWrite">
 +                                <a data-child-for="101251975" data-reply-to="baza">
 +                                    <svg class="resWriteIcon">
 +                                        <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#reply"></use>
 +                                    </svg>
 +                                    Odpowiedz</a>
 +                            </div>
 +
 +                            
 +                                <div class="cResShow">
 +                                    <a data-res-counter="2" data-state="show">
 +                                        <svg class="resShowIcon">
 +                                            <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#comment"></use>
 +                                        </svg>
 +                                        
 +                                            
 +                                            
 +                                                 Poka odpowiedzi (2)
 +                                            
 +                                        
 +                                    </a>
 +                                </div>
 +                            
 +                        </div>
 +
 +
 +                        
 +                            <!-- Odpowiedzi na komentarz -->
 +                            
 +                                <!-- komentarz zagniezdzony -->
 +                                <div class="cRow cResComment flag_PUBLISHED" id="opinion101252012"
 +                                     itemscope itemtype="http://schema.org/Comment" itemid="#opinion:101252012">
 +                                    <div class="cHead">
 +                                        
 +                                        <span itemprop="author" itemscope itemtype="http://schema.org/Person"><span class="cName" itemprop="name">
 +                                            
 +                                            
 +                                            
 +                                                vocativus
 +                                            
 +                                        </span></span>
 +                                        
 +                                        <span class="cDate" itemprop="datePublished"
 +                                              content="2019-01-31">31.01.2019, 16:15</span>
 +                                        
 +                                        
 +                                    </div>
 +                                    <div class="cBody">
 +                                        
 +                                            
 +                                        
 +                                        <span itemprop="text">@baza<br/>oczyci nieznacznie z substancji lotnych, ale nie z pyw</span></div>
 +                                    <div class="cFt">
 +                                        
 +                                            <!-- komentarze_ocenianie ON -->
 +                                            <div class="cAlreadyVoted"><span class="cVotedInfo">ju oceniae(a)</span></div>
 +                                            <div class="cVoteUp">
 +                                                <a data-action="vote-addPlus" data-post-id="101252012">
 +                                                    <svg class="voteUpIcon">
 +                                                        <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#vote-up"></use>
 +                                                    </svg>
 +                                                </a>
 +                                                <span itemprop="upvoteCount">0</span>
 +                                            </div>
 +
 +                                            <div class="cVoteDown">
 +                                                <a data-action="vote-addMinus" data-post-id="101252012">
 +                                                    <svg class="voteDownIcon">
 +                                                        <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#vote-down"></use>
 +                                                    </svg>
 +                                                </a>
 +                                                <span itemprop="downvoteCount">0</span>
 +                                            </div>
 +                                        
 +                                        <div class="cVoteSpam">
 +                                            <a title="Zgo do moderacji" data-action="trash-vote" data-post-id="101252012">
 +                                                <svg class="voteSpamIcon">
 +                                                    <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#spam"></use>
 +                                                </svg>
 +                                            </a>
 +                                        </div>
 +                                        <div class="cResWrite">
 +                                            <a data-child-for="101251975" data-reply-to="vocativus">
 +                                                <svg class="resWriteIcon">
 +                                                    <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#reply"></use>
 +                                                </svg>
 +                                                Odpowiedz</a>
 +                                        </div>
 +                                    </div>
 +                                </div>
 +                                <!-- / komentarz zagniezdzony -->
 +                            
 +                                <!-- komentarz zagniezdzony -->
 +                                <div class="cRow cResComment flag_PUBLISHED" id="opinion101252134"
 +                                     itemscope itemtype="http://schema.org/Comment" itemid="#opinion:101252134">
 +                                    <div class="cHead">
 +                                        
 +                                        <span itemprop="author" itemscope itemtype="http://schema.org/Person"><span class="cName" itemprop="name">
 +                                            
 +                                            
 +                                            
 +                                                Ardalis
 +                                            
 +                                        </span></span>
 +                                        
 +                                        <span class="cDate" itemprop="datePublished"
 +                                              content="2019-01-31">31.01.2019, 16:44</span>
 +                                        
 +                                        
 +                                    </div>
 +                                    <div class="cBody">
 +                                        
 +                                            
 +                                        
 +                                        <span itemprop="text">@baza<br/>Filtr wglowy nie suy do zatrzymywania pyw. Jeeli sam chcesz zrobi filtr smogu, czyli czstek okoo 1 mikrona potrzebujesz wkniny min klasy F9 a lepiej E10-11. Wtedy niestety wzrastaj opory przepywu i bdziesz potrzebowa wentylatora nieco wyszych cinie, np limakowego. Wszystko to oczywicie trzeba uszczelini. Taniej moe by jednak kupi gotowy.</span></div>
 +                                    <div class="cFt">
 +                                        
 +                                            <!-- komentarze_ocenianie ON -->
 +                                            <div class="cAlreadyVoted"><span class="cVotedInfo">ju oceniae(a)</span></div>
 +                                            <div class="cVoteUp">
 +                                                <a data-action="vote-addPlus" data-post-id="101252134">
 +                                                    <svg class="voteUpIcon">
 +                                                        <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#vote-up"></use>
 +                                                    </svg>
 +                                                </a>
 +                                                <span itemprop="upvoteCount">1</span>
 +                                            </div>
 +
 +                                            <div class="cVoteDown">
 +                                                <a data-action="vote-addMinus" data-post-id="101252134">
 +                                                    <svg class="voteDownIcon">
 +                                                        <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#vote-down"></use>
 +                                                    </svg>
 +                                                </a>
 +                                                <span itemprop="downvoteCount">0</span>
 +                                            </div>
 +                                        
 +                                        <div class="cVoteSpam">
 +                                            <a title="Zgo do moderacji" data-action="trash-vote" data-post-id="101252134">
 +                                                <svg class="voteSpamIcon">
 +                                                    <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#spam"></use>
 +                                                </svg>
 +                                            </a>
 +                                        </div>
 +                                        <div class="cResWrite">
 +                                            <a data-child-for="101251975" data-reply-to="Ardalis">
 +                                                <svg class="resWriteIcon">
 +                                                    <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#reply"></use>
 +                                                </svg>
 +                                                Odpowiedz</a>
 +                                        </div>
 +                                    </div>
 +                                </div>
 +                                <!-- / komentarz zagniezdzony -->
 +                            
 +
 +                        
 +
 +
 +                    </div>
 +                    <!-- / komentarz -->
 +
 +                
 +            </section>
 +            <!-- /.oBody -->
 +        
 +
 +        <footer class="oFooter">
 +            <div class="oFooterContainer">
 +                <p class="oFooterParagraph">
 +                    <a data-action="loadAllComments" title="Zobacz wicej" class="oMoreBtn">Wicej</a>
 +                </p>
 +            </div>
 +        </footer>
 +        <!-- /.oFooter -->
 +
 +    </section>
 +    <!-- / KOMENTARZE -->
 +
 +
 +<img id="activity-check" src="/fix/modules/wyborcza/comments/activity.jsp">
 + +<!--9638926, [ /fix/modules/opinions20.jsp ], opinions20PojoBean--> +
 +                    </div>
 +                </div>
 +
 +                <section class="complementary-entries">
 +                    <div class="first-bottom-content-holder column-54" data-position="54,12-15">
 +                        
 +                        
 +                        
 +                        
 +                    </div>
 +                </section>
 +            </section>
 +
 +            
 +                <aside class="article-extra grid-col-1-4 sidebar">
 +                    <div class="right-content-holder column-5 splint-glow" data-position="5,1-5 12-15">
 +                        <div class="mcBan" id="newsletterDM"></div>
 +
 +                        <!--n.cz., tr:#TRNajCzytSST--><article class="mod mod_most_read mod_most_read1 stare-najczesciej-czytane"><header>Najczciej Czytane</header><section class="body"><ul><li class=""><span class="number">1.</span><h3 class="title"><a href="http://wyborcza.biz/biznes/1,147758,14592095,Pokazali_apartamenty_w_Zlotej_44__Cena__65_tys__za.html#TRNajCzytSST" title="Pokazali apartamenty w Zotej 44. Cena: 65 tys. za metr">Pokazali apartamenty w Zotej 44. Cena: 65 tys. za metr</a></h3></li><li class=""><span class="number">2.</span><h3 class="title"><a href="http://wyborcza.biz/biznes/1,147758,12670651,Najwezszy_dom_swiata_prawie_gotowy__ZDJECIA_.html#TRNajCzytSST" title="Najwszy dom wiata prawie gotowy [ZDJCIA]">Najwszy dom wiata prawie gotowy [ZDJCIA]</a></h3></li><li class=""><span class="number">3.</span><h3 class="title"><a href="http://wyborcza.biz/biznes/7,147758,24397604,galopujace-ceny-mieszkan-zderza-sie-ze-sciana-eksperci-podpowiadaja.html#TRNajCzytSST" title="Deweloperzy buduj coraz drosze mieszkania. Klienci kupuj nawet dziury w ziemi">Deweloperzy buduj coraz drosze mieszkania. Klienci kupuj nawet dziury w ziemi</a></h3></li><li class=""><span class="number">4.</span><h3 class="title"><a href="http://wyborcza.biz/biznes/1,147582,18379055,najwieksze-ofiary-amber-gold-kto-stracil-miliony-w-parabanku.html#TRNajCzytSST" title="Najwiksze ofiary Amber Gold. Sprawdzilimy, kto straci miliony w parabanku">Najwiksze ofiary Amber Gold. Sprawdzilimy, kto straci miliony w parabanku</a></h3></li><li class=""><span class="number">5.</span><h3 class="title"><a href="http://wyborcza.biz/biznes/1,147758,17989403,Najlepszy_projekt_wnetrza_biurowca_na_wodzie.html#TRNajCzytSST" title="Najlepszy projekt wntrza biurowca na wodzie">Najlepszy projekt wntrza biurowca na wodzie</a></h3></li><li class=""><span class="number">6.</span><h3 class="title"><a href="http://wyborcza.biz/biznes/7,147743,22836294,ile-kosztuja-wlosy-nawet-8-tys-zl-za-kg-tyle-mozna-zarobic.html#TRNajCzytSST" title="Ile kosztuj wosy? Nawet 8 tys. z za 1 kg. Tyle mona zarobi ">Ile kosztuj wosy? Nawet 8 tys. z za 1 kg. Tyle mona zarobi </a></h3></li></ul></section></article> +<!--21406650, [ /htm/21406/j21406650.htm ], null--> +
 +                        
 +                        <!-- reklama -->
 +                        <div id="rectangle-splint" class="splint-element">
 +                            
 +
 +
 +<div id="003-RECTANGLE" class="adviewDFPBanner DFP-003-RECTANGLE">
 +    <span class="banLabel" style="display: none;">REKLAMA</span>
 +	<div id='div-gpt-ad-003-RECTANGLE-0'>
 +    <script type='text/javascript'>
 +    	if(dfpParams.slots['003-RECTANGLE'] && dfpParams.slots['003-RECTANGLE'].autoLoad) {
 +    		if (adviewDFP && adviewDFP.scrollSlot) adviewDFP.scrollSlot.push('003-RECTANGLE'); 
 +		} else {
 +		    googletag.cmd.push(function() { googletag.display('div-gpt-ad-003-RECTANGLE-0'); });
 +		}
 +       
 +    </script>
 +    </div>
 +</div>
 +
 +
 +<!-- v2.2 --> +<!--10185261, [ /tpl/ads/prod/dfpSlot.jsp ], dfpBanersSlotBean--> +
 +                        </div>
 +                        
 +                        <section class="mod-generic mod-generic-type-b-no-paddings mod-generic-type-b  box-cor mod-opinions top-margin-layout la-http://wyborcza.pl/0,75968.html#TRNavSST"> + +<ul class="mod-generic-items"> +<li class="mod-generic-item +mod-generic-item-other-with-photo +"> +<div class="mod-generic-item-content"> +<figure class="mod-generic-item-photo-wrapper"> +<a id="LinkArea:http://wyborcza.pl/0,75968.html#TRNavSST" href="http://wyborcza.biz/biznes/7,157729,24377175,kolejne-branze-na-celowniku-fiskusa.html" class="mod-generic-item-photo-wrapper" title="Kolejne brane na celowniku fiskusa Budowlanka, gastronomia, motoryzacja – objte specjalnym nadzorem fiskalnym"> +<img class="mod-generic-item-photo-wrapper-image" data-src="//bi.im-g.pl/im/e5/3f/17/z24377317J.jpg" alt="Kolejne brane na celowniku fiskusa Budowlanka, gastronomia, motoryzacja – objte specjalnym nadzorem fiskalnym" /> +</a> +</figure> +<span class="rekl-text">Materiay promocyjne partnera</span> +<a class="mod-generic-title-container" id="LinkArea:http://wyborcza.pl/0,75968.html#TRNavSST" href="http://wyborcza.biz/biznes/7,157729,24377175,kolejne-branze-na-celowniku-fiskusa.html" title="Kolejne brane na celowniku fiskusa Budowlanka, gastronomia, motoryzacja – objte specjalnym nadzorem fiskalnym"> +<span class="mod-generic-title">Kolejne brane na celowniku fiskusa Budowlanka, gastronomia, motoryzacja – objte specjalnym nadzorem fiskalnym</span> +</a> +</div> +</li> +</ul> +</section><!-- UZREditor --><!-- htmEOF --> +<!--22243333, [ /htm/22243/j22243333.htm ], null--> +
 +
 +                        <div id="rectangle-splint-067" class="splint-element-small">
 +                            
 +
 +
 +<div id="067-RECTANGLE-BTF" class="adviewDFPBanner DFP-067-RECTANGLE-BTF">
 +    <span class="banLabel" style="display: none;">REKLAMA</span>
 +	<div id='div-gpt-ad-067-RECTANGLE-BTF-0'>
 +    <script type='text/javascript'>
 +    	if(dfpParams.slots['067-RECTANGLE-BTF'] && dfpParams.slots['067-RECTANGLE-BTF'].autoLoad) {
 +    		if (adviewDFP && adviewDFP.scrollSlot) adviewDFP.scrollSlot.push('067-RECTANGLE-BTF'); 
 +		} else {
 +		    googletag.cmd.push(function() { googletag.display('div-gpt-ad-067-RECTANGLE-BTF-0'); });
 +		}
 +       
 +    </script>
 +    </div>
 +</div>
 +
 +
 +<!-- v2.2 --> +<!--11455919, [ /tpl/ads/prod/dfpSlot.jsp ], dfpBanersSlotBean--> +
 +                        </div>
 +
 +                        
 +                        
 +                        <!-- reklama -->
 +                        <div id="rectangle-splint-035" class="splint-element-small">
 +                            
 +
 +
 +<div id="035-RECTANGLE-BTF" class="adviewDFPBanner DFP-035-RECTANGLE-BTF">
 +    <span class="banLabel" style="display: none;">REKLAMA</span>
 +	<div id='div-gpt-ad-035-RECTANGLE-BTF-0'>
 +    <script type='text/javascript'>
 +    	if(dfpParams.slots['035-RECTANGLE-BTF'] && dfpParams.slots['035-RECTANGLE-BTF'].autoLoad) {
 +    		if (adviewDFP && adviewDFP.scrollSlot) adviewDFP.scrollSlot.push('035-RECTANGLE-BTF'); 
 +		} else {
 +		    googletag.cmd.push(function() { googletag.display('div-gpt-ad-035-RECTANGLE-BTF-0'); });
 +		}
 +       
 +    </script>
 +    </div>
 +</div>
 +
 +
 +<!-- v2.2 --> +<!--10185263, [ /tpl/ads/prod/dfpSlot.jsp ], dfpBanersSlotBean--> +
 +                        </div>
 +                        
 +                        
 +                        
 +                    </div>
 +                </aside>
 +            
 +
 +        
 +
 +    
 +            </div>
 +	    </main>
 +    
 +
 +    
 +
 +	<section class="ads container-outer">
 +		<div class="container-inner">
 +			<div class="grid-row">
 +				<div class="grid-col-wide">
 +					
 +
 +
 +<div id="042-FOOTBOARD" class="adviewDFPBanner DFP-042-FOOTBOARD">
 +    <span class="banLabel" style="display: none;">REKLAMA</span>
 +	<div id='div-gpt-ad-042-FOOTBOARD-0'>
 +    <script type='text/javascript'>
 +    	if(dfpParams.slots['042-FOOTBOARD'] && dfpParams.slots['042-FOOTBOARD'].autoLoad) {
 +    		if (adviewDFP && adviewDFP.scrollSlot) adviewDFP.scrollSlot.push('042-FOOTBOARD'); 
 +		} else {
 +		    googletag.cmd.push(function() { googletag.display('div-gpt-ad-042-FOOTBOARD-0'); });
 +		}
 +       
 +    </script>
 +    </div>
 +</div>
 +
 +
 +<!-- v2.2 --> +<!--10185264, [ /tpl/ads/prod/dfpSlot.jsp ], dfpBanersSlotBean--> +
 +				</div>
 +			</div>
 +		</div>
 +	</section>
 +
 +	<div class="container-inner">
 +        
 +            <div class="grid-row">
 +                <div class="grid-col-3-4">
 +                    <section class="complementary-entries">
 +                        <div class="second-bottom-content-holder column-57" data-position="57,1-5 12-15">
 +                            
 +                            
 +                            
 +                            
 +                            
 +                            
 +                            
 +                            
 +                            
 +                        </div>
 +                    </section>
 +                </div>
 +            </div>
 +        
 +
 +
 +		<div class="grid-row">
 +			<div class="grid-col-4-4 column-61" data-position="61,1-15">
 +				<div class="karaluch"> +<header> +<span>Wiadomoci - najwaniejsze informacje</span> +<i class="l"></i> +<i class="r"></i> +</header> +<section> +<header> +<span>Wiadomoci</span> +</header> +<ul> +<li><a href="http://wyborcza.pl/TylkoZdrowie/7,137474,23080156,dash-najzdrowsza-dieta-swiata.html" title="Dash: najzdrowsza dieta">Dash: najzdrowsza dieta</a></li><li><a href="http://biqdata.wyborcza.pl/biqdata/7,159116,23102688,ta-pierwsza-niehandlowa-niedziela-czyli-kiedy-nie-zrobisz-zakupow.html" title="Zakaz handlu w niedziele: kalendarz">Zakaz handlu w niedziele: kalendarz</a></li><li><a href="http://cojestgrane24.wyborcza.pl/cjg24/1,42.html" title="Muzea i galerie">Muzea i galerie</a></li><li><a href="http://wyborcza.pl/7,155287,22875272,teresa-czerwinska-bezpartyjny-fachowiec-kim-jest-nowa-ministra.html" title="Teresa Czerwiska">Teresa Czerwiska</a></li><li><a href="http://wyborcza.pl/7,75398,22876010,deklaracja-wiary-prof-szumowskiego-nowy-minister-zdrowia-wrogiem.html" title="ukasz Szumowski">ukasz Szumowski</a></li><li><a href="http://wyborcza.pl/7,155287,22873972,andrzej-adamczyk-minister-infrastruktury.html" title="Andrzej Adamczyk">Andrzej Adamczyk</a></li><li><a href="http://wyborcza.pl/7,155287,22874245,jadwiga-emilewicz-kim-jest-nowa-ministra-przedsiebiorczosci.html" title="Jadwiga Emilewicz">Jadwiga Emilewicz</a></li><li><a href="http://wyborcza.pl/7,155287,22875049,henryk-kowalczyk-szara-eminencja-pis-odpowiada-teraz-za-lasy.html" title="Henryk Kowalczyk">Henryk Kowalczyk</a></li><li><a href="http://wyborcza.pl/7,155287,22875507,jacek-sasin-nowym-szefem-komitetu-stalego-rady-ministrow-to.html" title="Jacek Sasin">Jacek Sasin</a></li><li><a href="http://wyborcza.pl/7,75410,22875881,paszporty-polityki-2017-tegoroczni-laureaci-jagoda-szelc.html" title="Paszporty Polityki 2017: laureaci">Paszporty Polityki 2017: laureaci</a></li><li><a href="http://bydgoszcz.wyborcza.pl/bydgoszcz/7,48722,22875851,szarza-z-widelcem-zakonczona-kownacki-na-wylocie-z-rzadu.html" title="Bartosz Kownacki">Bartosz Kownacki</a></li><li><a href="http://wyborcza.pl/7,75400,22873943,wajrak-minister-jan-szyszko-przejdzie-do-historii-jako-wyjatkowy.html?disableRedirects=true" title="Jan Szyszko">Jan Szyszko</a></li><li><a href="http://wyborcza.pl/7,156282,22874046,totalne-zaskoczenie-w-resorcie-kazdy-chwycil-za-telefon.html?disableRedirects=true" title="Ministerstwo cyfryzacji: Anna Streyska">Ministerstwo cyfryzacji: Anna Streyska</a></li><li><a href="http://wyborcza.pl/7,155287,22874886,nowy-minister-inwestycji-i-rozwoju-unijny-ekspert-jerzy-kwiecinski.html" title="Jerzy Kwieciski">Jerzy Kwieciski</a></li><li><a href="http://lodz.wyborcza.pl/lodz/7,35136,22875801,cezary-grabarczyk-zrezygnowalem-z-obrony-immunitetem.html" title="Cezary Grabarczyk">Cezary Grabarczyk</a></li><li><a href="http://wyborcza.pl/ksiazki/7,154165,22643551,wybralismy-10-ksiazek-roku-2017.html" title="Ksiki roku 2017">Ksiki roku 2017</a></li> +</ul> +</section><section> +<header> +<span>Tematy</span> +</header> +<ul> +<li><a href="http://wyborcza.pl/0,128956.html?tag=barbara+dziuk" title="Barbara Dziuk">Barbara Dziuk</a></li><li><a href="http://wyborcza.pl/0,128956.html?tag=Marek+P%EAk" title="Marek Pk">Marek Pk</a></li><li><a href="http://wyborcza.pl/0,128956.html?tag=Miros%B3aw+Pampuch" title="Mirosaw Pampuch">Mirosaw Pampuch</a></li><li><a href="http://wyborcza.pl/1,75398,19217826,posel-marek-opiola-o-nocnym-odwolaniu-szefow-sluzb-poslowie.html" title="Marek Opioa">Marek Opioa</a></li><li><a href="http://wyborcza.pl/magazyn/7,124059,22222760,czy-czeslaw-milosz-moze-byc-rzeczywiscie-zagrozeniem-dla.html" title="Czesaw Miosz">Czesaw Miosz</a></li><li><a href="http://wyborcza.pl/0,143644.html" title="Wybory samorzdowe 2018">Wybory samorzdowe 2018</a></li><li><a href="http://wyborcza.pl/56,140981,21698092,brigitte-macron.html" title="Brigitte Macron">Brigitte Macron</a></li><li><a href="http://wyborcza.pl/0,128956.html?tag=kleszcze" title="Kleszcze">Kleszcze</a></li><li><a href="http://wyborcza.pl/0,128956.html?tag=brexit+pytania" title="Brexit: pytania i odpowiedzi">Brexit: pytania i odpowiedzi</a></li><li><a href="http://wyborcza.pl/7,156282,21960784,od-dzisiaj-roaming-w-unii-europejskiej-przestal-obowiazywac.html" title="Roaming">Roaming</a></li><li><a href="http://warszawa.wyborcza.pl/warszawa/7,54420,21888478,rekrutacja-do-liceow-rekord-to-55-chetnych-na-miejsce.html" title="Rekrutacja do licew">Rekrutacja do licew</a></li><li><a href="http://wyborcza.biz/biznes/7,147743,21939448,abonament-rtv-wszystko-co-trzeba-wiedziec-w-kilku-prostych.html" title="Abonament RTV">Abonament RTV</a></li><li><a href="http://wyborcza.pl/7,75398,20946585,marks-spencer-zamyka-wszystkie-sklepy-w-polsce.html" title="Marks & Spencer">Marks & Spencer</a></li><li><a href="http://wyborcza.pl/alehistoria/1,121681,17844725,Ile_milionow_zginelo__Ofiary_II_wojny_swiatowej.html" title="Ofiary II wojny wiatowej">Ofiary II wojny wiatowej</a></li><li><a href="http://wyborcza.pl/1,97654,21220741,uzytkowanie-wieczyste-2017.html" title="Uytkowanie wieczyste 2017">Uytkowanie wieczyste 2017</a></li> +</ul> +</section><section> +<header> +<span>Informatory</span> +</header> +<ul> +<li><a href="http://wyborcza.pl/1,97654,21282743,zasilki-na-dzieci-w-2017-roku.html" title="Zasiek na dziecko 2017">Zasiek na dziecko 2017</a></li><li><a href="http://wyborcza.pl/7,97654,22392210,koniec-uzytkowania-wieczystego-informator.html" title="Uytkowanie wieczyste">Uytkowanie wieczyste</a></li><li><a href="http://wyborcza.biz/biznes/7,147880,21895075,waloryzacja-emerytur-w-2018-r-juz-wiemy-ile-wyniesie.html" title="Waloryzacja emerytur">Waloryzacja emerytur</a></li><li><a href="http://wyborcza.pl/7,75398,21968688,egzamin-gimnazjalny-2017-centralna-komisja-egzaminacyjna-podala.html" title="Egzamin gimnazjalny 2017: wyniki">Egzamin gimnazjalny 2017: wyniki</a></li><li><a href="http://wyborcza.pl/7,155287,21921609,placa-minimalna-w-2018-r-2080-zl-stawka-za-godzine-13-5.html" title="Paca minimalna 2018">Paca minimalna 2018</a></li><li><a href="http://wyborcza.pl/7,75398,20946585,marks-spencer-zamyka-wszystkie-sklepy-w-polsce.html" title="Marks & Spencer">Marks & Spencer</a></li><li><a href="http://wyborcza.pl/1,75398,20349918,jak-swiadek-koronny-masa-zostal-odtajniony-czy-doszlo-do.html" title="Masa odtajniony">Masa odtajniony</a></li><li><a href="http://wyborcza.pl/1,75399,20330335,egipt-jechac-czy-nie-jechac.html" title="Egipt: czy jecha?">Egipt: czy jecha?</a></li><li><a href="http://wyborcza.pl/1,75398,20046370,litewska-zbrodnia-majora-lupaszki.html" title="upaszka">upaszka</a></li><li><a href="http://wyborcza.pl/alehistoria/1,121681,17844725,Ile_milionow_zginelo__Ofiary_II_wojny_swiatowej.html" title="Ofiary II wojny wiatowej">Ofiary II wojny wiatowej</a></li><li><a href="http://wyborcza.pl/1,97654,21220741,uzytkowanie-wieczyste-2017.html" title="Uytkowanie wieczyste 2017">Uytkowanie wieczyste 2017</a></li><li><a href="http://wyborcza.pl/1,75248,15262278,Szybszy_internet_w_siedmiu_krokach__Co_zrobic__by.html" title="Szybszy internet">Szybszy internet</a></li><li><a href="http://wyborcza.pl/nekrologi/1,101500,18747235,kondolencje-podziekowania-wzory.html" title="Wzory: kondolencje - podzikowania">Wzory: kondolencje - podzikowania</a></li><li><a href="http://wyborcza.pl/1,97654,20849294,zmiany-w-emeryturach-z-krus.html" title="KRUS emerytura">KRUS emerytura</a></li><li><a href="http://wyborcza.pl/1,97654,20345805,komu-sie-nalezy-swiadczenie-przedemerytalne.html" title="wiadczenie przedemerytalne">wiadczenie przedemerytalne</a></li><li><a href="http://wyborcza.pl/1,97654,19802750,wczesniejsza-emerytura-nauczyciela.html" title="Wczeniejsza emerytura nauczyciela">Wczeniejsza emerytura nauczyciela</a></li><li><a href="http://wyborcza.pl/1,97654,19697904,jaki-podatek-od-nieruchomosci.html" title="Jaki podatek od nieruchomoci">Jaki podatek od nieruchomoci</a></li><li><a href="http://wyborcza.pl/1,97654,19533208,komu-sie-nalezy-renta-socjalna.html" title="Renta socjalna">Renta socjalna</a></li><li><a href="http://wyborcza.pl/1,97654,19344883,po-spadek-do-sadu-czy-do-notariusza.html" title="Spadek: sd czy notariusz">Spadek: sd czy notariusz</a></li><li><a href="http://wyborcza.pl/1,97654,19526974,komu-sie-nalezy-renta-rodzinna.html" title="Renta rodzinna">Renta rodzinna</a></li> +</ul> +</section><section> +<header> +<span>Nauka dla Kadego</span> +</header> +<ul> +<li><a href="http://wyborcza.pl/1,75248,6497648,Kleszcze__czyli_lesne_potwory.html" title="Kleszcze">Kleszcze</a></li><li><a href="http://wyborcza.pl/7,75400,21915146,polacy-rozszyfrowali-tajemnice-stwardnienia-rozsianego-mega-odkrycie.html" title="Stwardnienie rozsiane">Stwardnienie rozsiane</a></li><li><a href="http://wyborcza.pl/1,97654,19501961,przedawnienie-dlugow.html" title="Przedawnienie dugw">Przedawnienie dugw</a></li><li><a href="http://wyborcza.pl/1,97654,19139246,jak-czytac-wyniki-badan.html" title="Jak czyta wyniki bada">Jak czyta wyniki bada</a></li><li><a href="http://wyborcza.pl/1,97654,18726130,komu-sie-nalezy-dodatek-mieszkaniowy.html" title="Dodatek mieszkaniowy">Dodatek mieszkaniowy</a></li><li><a href="http://wyborcza.pl/1,97654,18083478,Jak_odzyskac_prawo_jazdy.html" title="Jak odzyska prawo jazdy">Jak odzyska prawo jazdy</a></li> +</ul> +</section><section> +<header> +<span>Tylko Zdrowie</span> +</header> +<ul> +<li><a href="http://wyborcza.pl/TylkoZdrowie/7,137474,22522336,ile-sie-zarabia-w-polskim-szpitalu-lista-plac.html" title="Szpital: Lista pac">Szpital: Lista pac</a></li><li><a href="http://wyborcza.pl/TylkoZdrowie/7,137474,22330718,rak-jelita-grubego-mozesz-mu-zapobiec-czy-badanie-jelita.html" title="Rak jelita grubego">Rak jelita grubego</a></li><li><a href="http://wyborcza.pl/TylkoZdrowie/7,137474,21879629,lody-weganskie-jak-je-zrobic.html" title="Lody wegaskie">Lody wegaskie</a></li><li><a href="http://wyborcza.pl/1,97654,17797883,Wazne_zmiany_w_kapitale_poczatkowym.html" title="Kapita pocztkowy zmiany">Kapita pocztkowy zmiany</a></li><li><a href="http://wyborcza.pl/1,97654,17787551,Emerytura_nauczyciela.html" title="Emerytura nauczyciela">Emerytura nauczyciela</a></li><li><a href="http://wyborcza.pl/1,97654,17414393,Za_ile_lat_emerytura_.html" title="Za ile lat emerytura?">Za ile lat emerytura?</a></li><li><a href="http://wyborcza.pl/1,97654,17264085,Spadek_i_darowizna_bez_placenia.html" title="Spadek i darowizna bez pacenia">Spadek i darowizna bez pacenia</a></li><li><a href="http://wyborcza.pl/1,97654,16322969,Testament_czy_darowizna__jak_przekazac_mieszkanie_.html" title="Testament czy darowizna">Testament czy darowizna</a></li><li><a href="http://wyborcza.pl/1,75398,19097048,235-miejsc-w-sejmie-dla-pis-pkw-podala-oficjalny-podzial-mandatow.html" title="Wyniki wyborw 2015 mandaty">Wyniki wyborw 2015 mandaty</a></li><li><a href="http://wyborcza.pl/7,75400,21726755,nadchodzi-wielki-atak-kleszczy.html" title="Kleszcze">Kleszcze</a></li><li><a href="http://wyborcza.pl/1,76842,7622197,Szumy_w_uszach__czyli_cisza__ktora_dzwoni_w_glowie.html" title="Szum w uszach">Szum w uszach</a></li> +</ul> +</section><section> +<header> +<span>Duy Format</span> +</header> +<ul> +<li><a href="http://wyborcza.pl/duzyformat/7,127290,22507549,angela-merkel-nas-zdradzila-dlaczego-6-milionow-niemcow.html" title="Angela Merkel">Angela Merkel</a></li><li><a href="http://wyborcza.pl/duzyformat/7,127290,22505059,policja-pod-willa-jaroslawa-kaczynskiego-policjanci-chronia.html" title="Jarosaw Kaczyski">Jarosaw Kaczyski</a></li><li><a href="http://wyborcza.pl/duzyformat/7,127290,21833158,jak-wstapilem-do-obrony-terytorialnej.html" title="Obrona Terytorialna">Obrona Terytorialna</a></li><li><a href="http://wyborcza.pl/1,76842,17920848,Fotelik_w_samochodzie___wazny_wzrost__a_nie_wiek_dziecka.html" title="Fotelik w samochodzie">Fotelik w samochodzie</a></li><li><a href="http://wyborcza.pl/1,97654,20039409,waloryzacja-emerytur-2017.html" title="Waloryzacja emerytur">Waloryzacja emerytur</a></li><li><a href="http://wyborcza.pl/TylkoZdrowie/1,137474,17101744,Dlaczego_masz_wzdety_brzuch.html" title="Wzdty brzuch">Wzdty brzuch</a></li> +</ul> +</section><section> +<header> +<span>Telewizyjna</span> +</header> +<ul> +<li><a href="http://wyborcza.pl/7,90535,20887556,wszystko-co-wiemy-o-3-sezonie-narcos-netflix-oglasza-obsade.html" title="Narcos">Narcos</a></li><li><a href="http://wyborcza.pl/TylkoZdrowie/1,137474,17101744,Dlaczego_masz_wzdety_brzuch.html" title="Wzdty brzuch">Wzdty brzuch</a></li> +</ul> +</section> +</div><!-- UZREditor --><!-- htmEOF --> +<!--17823192, [ /htm/17823/j17823192.htm ], null--> +
 +				
 +				<div class="karaluch"> +<header> +<span>Tematy</span> +<i class="l"></i> +<i class="r"></i> +</header> +<section> +<header> +<span>Wiadomoci</span> +</header> +<ul> +<li><a href="http://wyborcza.biz/biznes/7,147768,24265091,wazne-zmiany-jak-w-nowym-roku-rozliczyc-sie-z-urzedem-skarbowym.html" title="PIT 2018">PIT 2018</a></li><li><a href="http://wyborcza.biz/biznes/0,100969.html" title="Depesze agencyjne">Depesze agencyjne</a></li><li><a href="http://wyborcza.pl/0,128956.html?tag=500+z%B3+na+dziecko" title="500 z na dziecko">500 z na dziecko</a></li><li><a href="http://wyborcza.pl/0,148081.html" title="Uchodcy">Uchodcy</a></li><li><a href="http://wyborcza.biz/Gieldy/1,114507,19778208,ceny-ropy-ostro-w-gore-w-kwietniu-wielki-naftowy-pakt-rosji.html" title="Cena ropy">Cena ropy</a></li><li><a href="http://wyborcza.pl/0,105742.html" title="Katastrofa smoleska">Katastrofa smoleska</a></li> +</ul> +</section><section> +<header> +<span>Gieda i Waluty</span> +</header> +<ul> +<li><a href="http://wyborcza.biz/Gieldy/0,114507.html" title="Wiadomoci giedowe">Wiadomoci giedowe</a></li><li><a href="http://wyborcza.biz/Gieldy/0,114514.html" title="Notowania GPW">Notowania GPW</a></li><li><a href="http://wyborcza.biz/Waluty/0,111138,8932151,,,Kursy_srednie_walut_NBP,A.html" title="Kursy walut NBP">Kursy walut NBP</a></li><li><a href="http://wyborcza.biz/Gieldy/0,125867.html" title="Surowce">Surowce</a></li><li><a href="http://wyborcza.biz/Gieldy/0,114544.html" title="Sownik giedowy">Sownik giedowy</a></li> +</ul> +</section><section> +<header> +<span>Firma</span> +</header> +<ul> +<li><a href="http://wyborcza.biz/biznes/0,147582.html" title="Wiadomoci">Wiadomoci</a></li> +</ul> +</section><section> +<header> +<span>Przetargi</span> +</header> +<ul> +<li><a href="http://www.komunikaty.pl/komunikaty/0,80849.html" title="Przetargi Wiadomoci">Przetargi Wiadomoci</a></li><li><a href="http://www.komunikaty.pl/komunikaty/0,79968.html" title="Przetargi Vademecum">Przetargi Vademecum</a></li><li><a href="http://www.komunikaty.pl/komunikaty/i/kategoria-Przetargi/1" title="Przetargi ogoszenia">Przetargi ogoszenia</a></li><li><a href="http://www.komunikaty.pl/komunikaty/i/kategoria-Przetargi/przedmiot-roboty+budowlane/1" title="Przetargi budowlane">Przetargi budowlane</a></li><li><a href="http://www.komunikaty.pl/komunikaty/i/kategoria-Nieruchomo%C5%9Bci/procedura-licytacja/1" title="Licytacje nieruchomoci">Licytacje nieruchomoci</a></li><li><a href="http://www.komunikaty.pl/komunikaty/i/kategoria-Ruchomo%C5%9Bci/procedura-licytacja/1" title="Licytacje ruchomoci">Licytacje ruchomoci</a></li> +</ul> +</section><section> +<header> +<span>Gieda i waluty</span> +</header> +<ul> +<li><a href="http://wyborcza.biz/Gieldy/0,116736,,,,NAME,A.html" title="Katalog spek">Katalog spek</a></li><li><a href="http://wyborcza.biz/Waluty/0,111026.html" title="Stopy procentowe">Stopy procentowe</a></li><li><a href="http://wyborcza.biz/Gieldy/0,114543.html" title="Analiza techniczna">Analiza techniczna</a></li> +</ul> +</section> +</div><!-- UZREditor --><!-- htmEOF --> +<!--17661432, [ /htm/17661/j17661432.htm ], null--> +
 +				
 +				
 +				
 +				
 +				
 +				
 +				
 +				
 +				
 +				
 +				
 +				
 +			</div>
 +		</div>
 +
 +	</div>
 +
 +	<div class="container-outer page-footer">
 +		<div class="container-inner">
 +			<div class="grid-row column-59" data-position="59,1-15">
 +				
 +				
 +				
 +				
 +				
 +				
 +				
 +				
 +				
 +				
 +				
 +				
 +				
 +				
 +				
 +
 +
 +<div class="container-outer page-footer">
 +<div class="container-inner">
 +<div class="grid-row">
 +
 +    <div id="footer2"> +<section class="foot-summary"> +<header class="foot-summary-head"> +<h2 class="foot-summary-head-title">Wyborcza.pl</h2> +<a href="http://wyborcza.pl" title="Wyborcza.pl"> +<img src="http://static.im-g.pl/i/obrazki/wyborcza2015/svg/new_wyborcza_pl.svg" alt="Wyborcza.pl"> +</a> +</header> +<div class="foot-summary-big"> +<input type="checkbox" id="foot-summary-big-services-show" class="foot-summary-big-services-show"> +<nav class="foot-summary-big-services"> +<div class="foot-summary-big-services-block"> +<p class="foot-summary-big-services-block-name"> +<label for="foot-summary-big-services-block-show-0" title="Wyborcza.pl">Wyborcza.pl</label> +</p> +<input type="checkbox" id="foot-summary-big-services-block-show-0" class="foot-summary-big-services-block-show"> +<ul class="foot-summary-big-services-block-list"> +<li><a href="http://wyborcza.pl/0,75398.html" title="Kraj" >Kraj</a></li><li><a href="http://wyborcza.pl/0,75399.html" title="wiat" >wiat</a></li><li><a href="http://wyborcza.pl/0,75968.html" title="Opinie" >Opinie</a></li><li><a href="http://wyborcza.pl/0,155287.html" title="Gospodarka" >Gospodarka</a></li><li><a href="http://wyborcza.pl/0,75400.html" title="Nauka" >Nauka</a></li><li><a href="http://wyborcza.pl/0,156282.html" title="Technologia" >Technologia</a></li><li><a href="http://wyborcza.pl/0,75410.html" title="Kultura" >Kultura</a></li><li><a href="http://wyborcza.pl/0,154903.html" title="Sport" >Sport</a></li><li><a href="http://wyborcza.pl/0,82983.html" title="Wideo" >Wideo</a></li><li><a href="http://wyborcza.pl/0,160910.html" title="The Wall Street Journal" >The Wall Street Journal</a></li><li><a href="http://wyborcza.pl/0,87647.html" title="Witamy w Polsce" >Witamy w Polsce</a></li><li><a href="http://wyborcza.pl/0,160795.html" title="Wyborcza Classic" >Wyborcza Classic</a></li> +</ul> +</div><div class="foot-summary-big-services-block"> +<p class="foot-summary-big-services-block-name"> +<label for="foot-summary-big-services-block-show-1" title="Wyborcza.biz">Wyborcza.biz</label> +</p> +<input type="checkbox" id="foot-summary-big-services-block-show-1" class="foot-summary-big-services-block-show"> +<ul class="foot-summary-big-services-block-list"> +<li><a href="http://wyborcza.biz/biznes/0,149543.html" title="Aktualnoci" >Aktualnoci</a></li><li><a href="http://wyborcza.biz/Gieldy/0,114514.html" title="Gieda" >Gieda</a></li><li><a href="http://wyborcza.biz/Waluty/0,111138,8932151,,,Kursy_srednie_walut_NBP,A.html" title="Wymiana walut" >Wymiana walut</a></li><li><a href="http://wyborcza.biz/biznes/0,147582.html" title="Zakupy i finanse" >Zakupy i finanse</a></li><li><a href="http://wyborcza.biz/biznes/0,147880.html" title="ZUS i emerytury" >ZUS i emerytury</a></li><li><a href="http://wyborcza.biz/biznes/0,147768.html" title="Podatki" >Podatki</a></li><li><a href="http://wyborcza.biz/biznes/0,159911.html" title="Praca" >Praca</a></li><li><a href="http://wyborcza.biz/biznes/0,156481.html#TRNavSST" title="Motoryzacja i podre" >Motoryzacja i podre</a></li><li><a href="http://wyborcza.biz/biznes/0,147758.html" title="Nieruchomoci" >Nieruchomoci</a></li> +</ul> +</div><div class="foot-summary-big-services-block"> +<p class="foot-summary-big-services-block-name"> +<label for="foot-summary-big-services-block-show-2" title="Serwisy lokalne">Serwisy lokalne</label> +</p> +<input type="checkbox" id="foot-summary-big-services-block-show-2" class="foot-summary-big-services-block-show"> +<ul class="foot-summary-big-services-block-list"> +<li><a href="http://bialystok.wyborcza.pl/bialystok/0,0.html" title="Biaystok" >Biaystok</a></li><li><a href="http://bielskobiala.wyborcza.pl/bielskobiala/0,0.html" title="Bielsko-Biaa" >Bielsko-Biaa</a></li><li><a href="http://bydgoszcz.wyborcza.pl/bydgoszcz/0,0.html" title="Bydgoszcz" >Bydgoszcz</a></li><li><a href="http://czestochowa.wyborcza.pl/czestochowa/0,0.html" title="Czstochowa" >Czstochowa</a></li><li><a href="http://gliwice.wyborcza.pl/gliwice/0,0.html" title="Gliwice" >Gliwice</a></li><li><a href="http://gorzow.wyborcza.pl/gorzow/0,0.html" title="Gorzw Wlkp." >Gorzw Wlkp.</a></li><li><a href="http://katowice.wyborcza.pl/katowice/0,0.html" title="Katowice" >Katowice</a></li><li><a href="http://kielce.wyborcza.pl/kielce/0,0.html" title="Kielce" >Kielce</a></li><li><a href="http://krakow.wyborcza.pl/krakow/0,0.html" title="Krakw" >Krakw</a></li><li><a href="http://lublin.wyborcza.pl/lublin/0,0.html" title="Lublin" >Lublin</a></li><li><a href="http://lodz.wyborcza.pl/lodz/0,0.html" title="d" >d</a></li><li><a href="http://olsztyn.wyborcza.pl/olsztyn/0,0.html" title="Olsztyn" >Olsztyn</a></li><li><a href="http://opole.wyborcza.pl/opole/0,0.html" title="Opole" >Opole</a></li><li><a href="http://plock.wyborcza.pl/plock/0,0.html" title="Pock" >Pock</a></li><li><a href="http://poznan.wyborcza.pl/poznan/0,0.html" title="Pozna" >Pozna</a></li><li><a href="http://radom.wyborcza.pl/radom/0,0.html" title="Radom" >Radom</a></li><li><a href="http://rzeszow.wyborcza.pl/rzeszow/0,0.html" title="Rzeszw" >Rzeszw</a></li><li><a href="http://sosnowiec.wyborcza.pl/sosnowiec/0,0.html" title="Sosnowiec" >Sosnowiec</a></li><li><a href="http://szczecin.wyborcza.pl/szczecin/0,0.html" title="Szczecin" >Szczecin</a></li><li><a href="http://torun.wyborcza.pl/torun/0,0.html" title="Toru" >Toru</a></li><li><a href="http://trojmiasto.wyborcza.pl/trojmiasto/0,0.html" title="Trjmiasto" >Trjmiasto</a></li><li><a href="http://warszawa.wyborcza.pl/warszawa/0,0.html" title="Warszawa" >Warszawa</a></li><li><a href="http://wroclaw.wyborcza.pl/wroclaw/0,0.html" title="Wrocaw" >Wrocaw</a></li><li><a href="http://zielonagora.wyborcza.pl/zielonagora/0,0.html" title="Zielona Gra" >Zielona Gra</a></li> +</ul> +</div><div class="foot-summary-big-services-block"> +<p class="foot-summary-big-services-block-name"> +<label for="foot-summary-big-services-block-show-3" title="Wysokieobcasy.pl">Wysokieobcasy.pl</label> +</p> +<input type="checkbox" id="foot-summary-big-services-block-show-3" class="foot-summary-big-services-block-show"> +<ul class="foot-summary-big-services-block-list"> +<li><a href="http://www.wysokieobcasy.pl/wysokie-obcasy/0,114757.html" title="Najnowsze" >Najnowsze</a></li><li><a href="http://www.wysokieobcasy.pl/wysokie-obcasy/0,100865.html" title="Gosy Kobiet" >Gosy Kobiet</a></li><li><a href="http://www.wysokieobcasy.pl/wysokie-obcasy/0,53664.html" title="Psychologia" >Psychologia</a></li><li><a href="http://www.wysokieobcasy.pl/wysokie-obcasy/0,66725.html" title="Wasze listy" >Wasze listy</a></li><li><a href="http://www.wysokieobcasy.pl/wysokie-obcasy/0,53662.html" title="Portrety Kobiet" >Portrety Kobiet</a></li><li><a href="http://www.wysokieobcasy.pl/wysokie-obcasy/0,53664.html" title="Psychologia" >Psychologia</a></li><li><a href="http://www.wysokieobcasy.pl/wysokie-obcasy/0,127763.html" title="Nowy Numer" >Nowy Numer</a></li><li><a href="http://www.wysokieobcasy.pl/wysokie-obcasy/0,152731.html" title="Wysokie Obcasy Extra" >Wysokie Obcasy Extra</a></li><li><a href="http://www.wysokieobcasy.pl/wysokie-obcasy/0,100961.html" title="Zdrowie i Uroda" >Zdrowie i Uroda</a></li><li><a href="http://www.wysokieobcasy.pl/wysokie-obcasy/0,53667.html" title="Jedzenie" >Jedzenie</a></li><li><a href="http://www.wysokieobcasy.pl/wysokie-obcasy/0,158669.html" title="IT Girls" >IT Girls</a></li> +</ul> +</div><div class="foot-summary-big-services-block"> +<p class="foot-summary-big-services-block-name"> +<label for="foot-summary-big-services-block-show-4" title="Magazyny">Magazyny</label> +</p> +<input type="checkbox" id="foot-summary-big-services-block-show-4" class="foot-summary-big-services-block-show"> +<ul class="foot-summary-big-services-block-list"> +<li><a href="http://wyborcza.pl/duzyformat/0,0.html#TRNavSST" title="Duy Format" >Duy Format</a></li><li><a href="http://wyborcza.pl/magazyn/0,0.html" title="Magazyn witeczny" >Magazyn witeczny</a></li><li><a href="http://wyborcza.pl/alehistoria/0,0.html" title="Ale Historia" >Ale Historia</a></li><li><a href="http://wyborcza.pl/TylkoZdrowie/0,0.html" title="Tylko zdrowie" >Tylko zdrowie</a></li><li><a href="http://wyborcza.pl/0,90535.html" title="Telewizyjna" >Telewizyjna</a></li><li><a href="http://wyborcza.pl/ksiazki/0,0.html" title="Ksiki" >Ksiki</a></li><li><a href="http://wyborcza.pl/osiemdziewiec/0,0.html" title="Osiem Dziewi" >Osiem Dziewi</a></li><li><a href="http://wyborcza.pl/0,79078.html" title="Poradniki" >Poradniki</a></li> +</ul> +</div> +</nav> +<div class="foot-summary-big-services-more"> +<label for="foot-summary-big-services-show">Wicej</label> +</div> +</div><div class="foot-summary-short"> +<ul class="foot-summary-short-links"> +<li><a href="http://biqdata.wyborcza.pl/" title="BIQdata.pl" >BIQdata.pl</a></li><li><a href="http://classic.wyborcza.pl/archiwumGW/0,0.html" title="Archiwum" >Archiwum</a></li><li><a href="http://www.komunikaty.pl" title="Komunikaty.pl" >Komunikaty.pl</a></li><li><a href="http://cojestgrane24.wyborcza.pl/cjg24/0,0.html" title="Cojestgrane24.pl" >Cojestgrane24.pl</a></li><li><a href="http://nekrologi.wyborcza.pl/0,0.html" title="Nekrologi" >Nekrologi</a></li> +</ul><div class="foot-summary-short-partners"> +<p class="foot-summary-short-partners-labels">Serwisy partnerskie</p> +<ul class="foot-summary-short-partners-list"> +<li><a href="http://gazeta.pl" title="Gazeta.pl" >Gazeta.pl</a></li><li><a href="http://www.tokfm.pl/Tokfm/0,0.html" title="TOK.fm" >TOK.fm</a></li><li><a href="http://sport.pl" title="Sport.pl" >Sport.pl</a></li><li><a href="http://publio.pl" title="Publio.pl" >Publio.pl</a></li><li><a href="http://kulturalnysklep.pl" title="Kulturalnysklep.pl" >Kulturalnysklep.pl</a></li> +</ul> +</div> +<a class="foot-summary-short-contact-link" href="http://wyborcza.pl/centrumpomocygw/0,134959.html" title="Napisz do redakcji"> +<figure> +<svg> +<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#letter"></use> +</svg> +</figure> +Napisz do redakcji +</a> +<div class="foot-summary-short-contact"> +<a class="foot-summary-short-contact-mail" href="mailto:redakcja@wyborcza.pl" title="Napisz do redakcji"> +<figure> +<svg> +<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#letter"></use> +</svg> +</figure> +Napisz do redakcji +</a> +</div> +</div><div class="foot-summary-buttons"> +<div class="foot-summary-buttons-subscription"> +<a class="foot-summary-buttons-subscription-link" title="Kup prenumerat" href="http://prenumerata.wyborcza.pl">Kup prenumerat</a> +</div> +<div class="foot-summary-buttons-stores"><a +href="https://itunes.apple.com/pl/app/gazeta-wyborcza/id530078918" +title="Aplikacja wyborcza.pl" +><svg> +<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#app-store"></use> +</svg></a><a +href="https://play.google.com/store/apps/details?id=pl.wyborcza.android.google&hl=pl" +title="Aplikacja wyborcza.pl" +><svg> +<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#google-play"></use> +</svg></a></div><!-- UZREditor --><!-- htmEOF -->
 +
 +
 +
 + 
 +    <ul class="foot-summary-buttons-share"> +<li class="foot-summary-buttons-share-face"> +<a href="https://www.facebook.com/wyborczabiznes" > +<figure> +<svg> +<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#face"></use> +</svg> +</figure> +</a> +</li><li class="foot-summary-buttons-share-tweet"> +<a href="https://twitter.com/wyborcza_biz" > +<figure> +<svg> +<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#tweet"></use> +</svg> +</figure> +</a> +</li> +<li class="foot-summary-buttons-share-newsletter" title="newsletter"> +<a href="http://wyborcza.pl/0,157210.html?fromnlt=stopka&refnlt=http://wyborcza.biz/">Newsletter</a> +</li> +</ul><!-- UZREditor --><!-- htmEOF -->
 +    <!-- test stopki modul jest-->
 +
 +
 +
 +
 +
 +    </div>
 +    <footer class="foot-menu">
 +        <nav class="foot-menu-links">
 +            <p class="foot-menu-links-copyright"><a href="http://www.agora.pl/" title="Agora SA">Copyright © Agora SA</a></p>
 +            <ul>
 +                
 +                    <li><a href="http://wyborcza.pl/centrumpomocygw/0,134955.html" title="O nas" >O nas</a></li><li><a href="http://wyborcza.pl/centrumpomocygw/7,134956,23413995,polityka-prywatnosci.html" title="Prywatno" >Prywatno</a></li><li><a href="http://wyborcza.pl/centrumpomocygw/1,134957,8856780,Ogolne_zasady_udzielania_licencji_do_Materialow_Redakcyjnych.html" title="Licencje/Kontent" >Licencje/Kontent</a></li><li><a href="http://wyborcza.pl/reklamaGW/0,156164.html" title="Reklama w Internecie" >Reklama w Internecie</a></li><li><a href="http://wyborcza.pl/reklamaGW/0,104163.html" title="Reklama w papierze" >Reklama w papierze</a></li><li><a href="http://wyborcza.pl/centrumpomocygw/0,134959.html" title="Kontakt" >Kontakt</a></li><li><a href="http://wyborcza.pl/centrumpomocygw/0,134958.html" title="Zgo bd" rel="nofollow">Zgo bd</a></li><li><a href="http://wyborcza.pl/centrumpomocygw/0,134958.html" title="Pomoc" >Pomoc</a></li><!-- UZREditor --><!-- htmEOF -->
 +                 
 +                 <!-- 21755068 -->
 +                <li><a href="http://wyborcza.biz/biznes/3660000,0.html" title="Wszystkie artykuy">Wszystkie artykuy</a></li>
 +            </ul>
 +        </nav>
 +    </footer>
 +</div>
 +</div>
 +</div>
 +</div>
 +
 +<!-- footerModule v1.0 --> +<!--22442678, [ /fix/modules/wyborcza/portal/footerModule.jsp ], wyborczaFooterModule--> +
 +			</div>
 +		</div>
 +	</div>
 +
 +
 +	
 +	
 +     + + + + +<!--13289483, [ /fix/modules/wyborcza/rodoAgreement.jsp ], rodoAgreementModule--> +
 +	
 +
 +
 +
 +
 +    <script type='text/javascript'>
 +        var _sf_async_config = _sf_async_config || {};
 +
 +        _sf_async_config.sections = '/biznes, kraj';
 +        _sf_async_config.authors = 'Micha Frk';
 +        var _cbq = window._cbq = (window._cbq || []);
 +        _cbq.push(['_acct', 'anon']);
 +
 +        (function() {
 +            function loadChartbeat() {
 +                window._sf_endpt = (new Date()).getTime();
 +                var e = document.createElement('script');
 +                e.setAttribute('language', 'javascript');
 +                e.setAttribute('type', 'text/javascript');
 +                e.setAttribute('src', '//static.chartbeat.com/js/chartbeat_video.js');
 +                document.body.appendChild(e);
 +            }
 +            var oldonload = window.onload;
 +            window.onload = (typeof window.onload != 'function') ? loadChartbeat
 +                    : function() {
 +                        oldonload();
 +                        loadChartbeat();
 +                    };
 +        })();
 +    </script>
 +
 +
 +
 +<!-- chartbeatModule v1.3 -->
 +
 +
 +
 +
 +
 +
 +<div style="display:none;">
 +<img height="1" width="1" style="border-style:none;" alt="" src="http://www.googleadservices.com/pagead/conversion/1039774788/?label=avQqCOa77QEQxOjm7wM&guid=ON&script=0"/>
 +</div>
 +
 +
 +<!-- remarketingModule -->
 +
 +
 +
 +
 +	
 +	
 +        <!-- pre-footer script -->
 +		<script>!function(){function a(){k(),b(),l()}function b(){var a=document.querySelectorAll("img["+p+"]:not(."+m+"):not(."+n+")");[].forEach.call(a,function(a){c(a)})}function c(a){var b,c,f=e(a);i(a)&&f&&!h(a,f)&&(a.addClass(n),c=document.createElement("div"),c.addClass(o),j(c,a),b=new Image,b.src=f,b.onload=function(e){d(a,b,c)})}function d(a,b,c){a.setAttribute("src",b.src),a.addClass(m),a.removeClass(n),a.removeAttribute(p),a.removeAttribute(q),c.remove()}function e(a){var b=a.getAttribute(p)||!1,c=a.getAttribute(q)||!1;return f()&&!g()&&c&&(b=c),b}function f(){var a=!1;return"undefined"!=typeof gazeta_pl&&void 0!==gazeta_pl.mobileInfo&&gazeta_pl.mobileInfo.hasOwnProperty("isMobileDevice")&&gazeta_pl.mobileInfo.isMobileDevice&&(a=!0),a}function g(){var a=!1;return"undefined"!=typeof gazeta_pl&&void 0!==gazeta_pl.tabletInfo&&gazeta_pl.tabletInfo.hasOwnProperty("isTabletDevice")&&!0===gazeta_pl.tabletInfo.isTabletDevice&&(a=!0),a}function h(a,b){return a.src===b&&(a.complete&&0!==a.naturalHeight)}function i(a){var b=a.getBoundingClientRect(),c=document.documentElement.scrollTop,d=b.top+c,e=a.clientHeight,f=window.innerHeight,g=c-f,h=c+f+f,i=d+e;return d>g&&i<h}function j(a,b){b.parentNode.insertBefore(a,b.nextSibling)}function k(){"remove"in Element.prototype||(Element.prototype.remove=function(){this.parentNode&&this.parentNode.removeChild(this)}),Element.prototype.addClass=function(a){this.classList.add(a)},Element.prototype.removeClass=function(a){this.classList.remove(a)}}function l(){["scroll","resize"].map(function(a){window.addEventListener(a,b)})}var m="loaded",n="processing",o="preloaderContainer",p="data-src",q="data-src-mobile";a()}();</script>
 +        <!-- /pre-footer script -->
 +	
 +
 +
 +
 +	
 +	
 +        <!-- wyborcza_common_skrypt not set -->
 +        
 +            
 +                <script type="text/javascript" src="//static.im-g.pl/info/wyborcza-common/builds/17.4.108/main-min.jsgz"></script>
 +            
 +            
 +        
 +	
 +
 +
 +
 +	
 +        <!-- desktop script -->
 +		<script type="text/javascript" src="//static.im-g.pl/wyborcza/wyborcza-biz/js/1.0.4/main-min.jsgz"></script>
 +        <!-- /desktop script -->
 +	
 +	
 +
 +
 +
 +	
 +	
 +        <!-- footer script -->
 +		<script></script>
 +        <!-- /footer script -->
 +	
 +
 +
 +<!-- scriptsModule v1.1 -->
 + + + +    <img src="https://pubads.g.doubleclick.net/activity;dc_iu=/75224259/DFPAudiencePixel;ord=1;dc_seg=692406053?" width=1 height=1 border=0/> + +<!-- singleViewModule --> + + +
 +
 +
 +
 +
 +<!-- deutscheWelleModule -->
 +
 +
 +
 +
 +
 +
 +
 +<!-- gemiusEfekt -->
 +
 +
 +
 +
 +
 +
 +
 +<!-- plistaModule-->
 +
 +
 + +<!--10185200, [ null ], aggregatorModule--> +
 +	
 +
 +
 +<div>
 +    <script id="adblock-modal" type="text/template"> +<div class="noadinfo"> +<div class="noadinfo-modal"> +<img src="https://bis.gazeta.pl/im/8/22327/m22327058.png" alt="" class="noadinfo-modal-image noadinfo-modal-image-desktop"/> +<img src="https://bis.gazeta.pl/im/7/22327/m22327057.png" alt="" class="noadinfo-modal-image noadinfo-modal-image-mobile"/> +<div class="noadinfo-modal-subscription">Jeeli jednak nie chcesz wycza adblocka, <a href="http://wyborcza.pl/prenumerataadb">kup prenumerat.</a> Zdecydowao si na to ju 100 tysicy internautw.</div> +<div class="noadinfo-modal-instruction"> +<div class="noadinfo-modal-show noadinfo-chrome"> +<label class="noadinfo-modal-instruction-tab noadinfo-modal-instruction-tab-active noadinfo-modal-instruction-tab-ab" for="option1">Mam Adblock</label> +<label class="noadinfo-modal-instruction-tab noadinfo-modal-instruction-tab-ap" for="option2">Mam Adblock Plus</label> +<div class="noadinfo-modal-instruction-holder"> +<input class="noadinfo-modal-input" type="radio" name="input-chrome" id="option1" checked /> +<div class="noadinfo-modal-detail"> +<p><b>Krok 1.</b> Kliknij w ikonk Adblocka obok pola do adresu oraz wybierz opcj: „Wstrzymaj blokowanie na stronach w tej domenie”.</p> +<img src="http://bi.gazeta.pl/im/4/22139/m22139974,CHROME-AB1.png" title="Instrukcja"> +<p><b>Krok 2.</b> Kliknij „Wyklucz”.</p> +<img src="http://bi.gazeta.pl/im/4/22156/m22156554,CHROME-AB2.png" title="Instrukcja"> +<p>Strona sama si odwiey i dostaniesz darmowe artykuy.</p> +</div> +<input class="noadinfo-modal-input" type="radio" name="input-chrome" id="option2" /> +<div class="noadinfo-modal-detail"> +<p><b>Krok 1.</b> Kliknij w ikonk Adblocka Plus obok pola do adresu oraz kliknij „Wczony na tej stronie”.</p> +<img src="http://bi.gazeta.pl/im/1/22139/m22139971,CHROME-AP1.png" title="Instrukcja"> +<p><b>Krok 2.</b> <a href="#" class="noadinfo-modal-instruction-reload" title="Odwie stron">Odwie stron</a>, aby dosta darmowe artykuy!</p> +</div> +</div> +</div> +<div class="noadinfo-modal-show noadinfo-firefox"> +<label class="noadinfo-modal-instruction-tab noadinfo-modal-instruction-tab-active noadinfo-modal-instruction-tab-ap" for="option3">Mam Adblock</label> +<label class="noadinfo-modal-instruction-tab noadinfo-modal-instruction-tab-ub" for="option4">Mam Ublock Origin</label> +<div class="noadinfo-modal-instruction-holder"> +<input class="noadinfo-modal-input" type="radio" name="input-firefox" id="option3" checked /> +<div class="noadinfo-modal-detail"> +<p><b>Krok 1.</b> Kliknij w ikonk Adblocka obok pola do adresu oraz kliknij: "Wycz blokowanie tylko na tej stronie".</p> +<img src="http://bi.gazeta.pl/im/2/22139/m22139972,FIREFOX-AP1.png" title="Instrukcja"> +<p><b>Krok 2.</b> <a href="#" class="noadinfo-modal-instruction-reload" title="Odwie stron">Odwie stron</a>, aby dosta darmowe artykuy!</p> +</div> +<input class="noadinfo-modal-input" type="radio" name="input-firefox" id="option4" /> +<div class="noadinfo-modal-detail"> +<p><b>Krok 1.</b> Kliknij w ikonk uBlock Origin obok pola do adresu oraz kliknij du niebiesk ikonk.</p> +<img src="http://bi.gazeta.pl/im/3/22139/m22139973,FIREFOX-UB1.png" title="Instrukcja"> +<p><b>Krok 2.</b> <a href="#" class="noadinfo-modal-instruction-reload" title="Odwie stron">Odwie stron</a>, aby dosta darmowe artykuy!</p> +</div> +</div> +</div> +</div> +</div> +</div> +</script><!-- UZREditor --><!--22060362,aliasOf--><!-- htmEOF -->
 +</div>
 +
 +<!-- adBlockModule -->
 + +<!--12059452, [ /fix/modules/wyborcza/portal/adBlockModule.jsp ], emptyBean--> +
 +
 +
 +	<script type="text/javascript" src="https://bis.gazeta.pl/info/mapa2.js"></script>
 +	<!-- gemiusHeatMap -->
 +
 +	<div class="mcBan" id="mcBan_4"></div>
 +	<div class="mcBan" id="mcBan_6"></div>
 +
 +    
 +        <div class="mcBan" id="glider1"></div>
 +	    <div class="mcBan" id="crawler1"></div>
 +    
 +
 +     + + + + +<!-- iSlayModule --> + +<!--14021450, [ /fix/modules/wyborcza/portal/iSlayModule.jsp ], iSlayModule--> +
 +</body>
 +</html>
 diff --git a/test/formatter_test.exs b/test/formatter_test.exs index 2e717194b..e74985c4e 100644 --- a/test/formatter_test.exs +++ b/test/formatter_test.exs @@ -21,22 +21,16 @@ defmodule Pleroma.FormatterTest do        expected_text =          "I love <a class='hashtag' data-tag='cofe' href='http://localhost:4001/tag/cofe' rel='tag'>#cofe</a> and <a class='hashtag' data-tag='2hu' href='http://localhost:4001/tag/2hu' rel='tag'>#2hu</a>" -      tags = Formatter.parse_tags(text) - -      assert expected_text == -               Formatter.add_hashtag_links({[], text}, tags) |> Formatter.finalize() +      assert {^expected_text, [], _tags} = Formatter.linkify(text)      end      test "does not turn html characters to tags" do -      text = "Fact #3: pleroma does what mastodon't" +      text = "#fact_3: pleroma does what mastodon't"        expected_text = -        "Fact <a class='hashtag' data-tag='3' href='http://localhost:4001/tag/3' rel='tag'>#3</a>: pleroma does what mastodon't" - -      tags = Formatter.parse_tags(text) +        "<a class='hashtag' data-tag='fact_3' href='http://localhost:4001/tag/fact_3' rel='tag'>#fact_3</a>: pleroma does what mastodon't" -      assert expected_text == -               Formatter.add_hashtag_links({[], text}, tags) |> Formatter.finalize() +      assert {^expected_text, [], _tags} = Formatter.linkify(text)      end    end @@ -47,79 +41,79 @@ defmodule Pleroma.FormatterTest do        expected =          "Hey, check out <a href=\"https://www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla\">https://www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla</a> ." -      assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected +      assert {^expected, [], []} = Formatter.linkify(text)        text = "https://mastodon.social/@lambadalambda"        expected =          "<a href=\"https://mastodon.social/@lambadalambda\">https://mastodon.social/@lambadalambda</a>" -      assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected +      assert {^expected, [], []} = Formatter.linkify(text)        text = "https://mastodon.social:4000/@lambadalambda"        expected =          "<a href=\"https://mastodon.social:4000/@lambadalambda\">https://mastodon.social:4000/@lambadalambda</a>" -      assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected +      assert {^expected, [], []} = Formatter.linkify(text)        text = "@lambadalambda"        expected = "@lambadalambda" -      assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected +      assert {^expected, [], []} = Formatter.linkify(text)        text = "http://www.cs.vu.nl/~ast/intel/"        expected = "<a href=\"http://www.cs.vu.nl/~ast/intel/\">http://www.cs.vu.nl/~ast/intel/</a>" -      assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected +      assert {^expected, [], []} = Formatter.linkify(text)        text = "https://forum.zdoom.org/viewtopic.php?f=44&t=57087"        expected =          "<a href=\"https://forum.zdoom.org/viewtopic.php?f=44&t=57087\">https://forum.zdoom.org/viewtopic.php?f=44&t=57087</a>" -      assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected +      assert {^expected, [], []} = Formatter.linkify(text)        text = "https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul"        expected =          "<a href=\"https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul\">https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul</a>" -      assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected +      assert {^expected, [], []} = Formatter.linkify(text)        text = "https://www.google.co.jp/search?q=Nasim+Aghdam"        expected =          "<a href=\"https://www.google.co.jp/search?q=Nasim+Aghdam\">https://www.google.co.jp/search?q=Nasim+Aghdam</a>" -      assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected +      assert {^expected, [], []} = Formatter.linkify(text)        text = "https://en.wikipedia.org/wiki/Duff's_device"        expected =          "<a href=\"https://en.wikipedia.org/wiki/Duff's_device\">https://en.wikipedia.org/wiki/Duff's_device</a>" -      assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected +      assert {^expected, [], []} = Formatter.linkify(text)        text = "https://pleroma.com https://pleroma.com/sucks"        expected =          "<a href=\"https://pleroma.com\">https://pleroma.com</a> <a href=\"https://pleroma.com/sucks\">https://pleroma.com/sucks</a>" -      assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected +      assert {^expected, [], []} = Formatter.linkify(text)        text = "xmpp:contact@hacktivis.me"        expected = "<a href=\"xmpp:contact@hacktivis.me\">xmpp:contact@hacktivis.me</a>" -      assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected +      assert {^expected, [], []} = Formatter.linkify(text)        text =          "magnet:?xt=urn:btih:7ec9d298e91d6e4394d1379caf073c77ff3e3136&tr=udp%3A%2F%2Fopentor.org%3A2710&tr=udp%3A%2F%2Ftracker.blackunicorn.xyz%3A6969&tr=udp%3A%2F%2Ftracker.ccc.de%3A80&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com"        expected = "<a href=\"#{text}\">#{text}</a>" -      assert Formatter.add_links({[], text}) |> Formatter.finalize() == expected +      assert {^expected, [], []} = Formatter.linkify(text)      end    end @@ -136,12 +130,9 @@ defmodule Pleroma.FormatterTest do        archaeme_remote = insert(:user, %{nickname: "archaeme@archae.me"}) -      mentions = Pleroma.Formatter.parse_mentions(text) - -      {subs, text} = Formatter.add_user_links({[], text}, mentions) +      {text, mentions, []} = Formatter.linkify(text) -      assert length(subs) == 3 -      Enum.each(subs, fn {uuid, _} -> assert String.contains?(text, uuid) end) +      assert length(mentions) == 3        expected_text =          "<span class='h-card'><a data-user='#{gsimg.id}' class='u-url mention' href='#{ @@ -152,7 +143,7 @@ defmodule Pleroma.FormatterTest do            archaeme_remote.id          }' class='u-url mention' href='#{archaeme_remote.ap_id}'>@<span>archaeme</span></a></span>" -      assert expected_text == Formatter.finalize({subs, text}) +      assert expected_text == text      end      test "gives a replacement for user links when the user is using Osada" do @@ -160,48 +151,60 @@ defmodule Pleroma.FormatterTest do        text = "@mike@osada.macgirvin.com test" -      mentions = Formatter.parse_mentions(text) +      {text, mentions, []} = Formatter.linkify(text) -      {subs, text} = Formatter.add_user_links({[], text}, mentions) - -      assert length(subs) == 1 -      Enum.each(subs, fn {uuid, _} -> assert String.contains?(text, uuid) end) +      assert length(mentions) == 1        expected_text =          "<span class='h-card'><a data-user='#{mike.id}' class='u-url mention' href='#{mike.ap_id}'>@<span>mike</span></a></span> test" -      assert expected_text == Formatter.finalize({subs, text}) +      assert expected_text == text      end      test "gives a replacement for single-character local nicknames" do        text = "@o hi"        o = insert(:user, %{nickname: "o"}) -      mentions = Formatter.parse_mentions(text) - -      {subs, text} = Formatter.add_user_links({[], text}, mentions) +      {text, mentions, []} = Formatter.linkify(text) -      assert length(subs) == 1 -      Enum.each(subs, fn {uuid, _} -> assert String.contains?(text, uuid) end) +      assert length(mentions) == 1        expected_text =          "<span class='h-card'><a data-user='#{o.id}' class='u-url mention' href='#{o.ap_id}'>@<span>o</span></a></span> hi" -      assert expected_text == Formatter.finalize({subs, text}) +      assert expected_text == text      end      test "does not give a replacement for single-character local nicknames who don't exist" do        text = "@a hi" -      mentions = Formatter.parse_mentions(text) +      expected_text = "@a hi" +      assert {^expected_text, [] = _mentions, [] = _tags} = Formatter.linkify(text) +    end + +    test "given the 'safe_mention' option, it will only mention people in the beginning" do +      user = insert(:user) +      _other_user = insert(:user) +      third_user = insert(:user) +      text = " @#{user.nickname} hey dude i hate @#{third_user.nickname}" +      {expected_text, mentions, [] = _tags} = Formatter.linkify(text, safe_mention: true) -      {subs, text} = Formatter.add_user_links({[], text}, mentions) +      assert mentions == [{"@#{user.nickname}", user}] -      assert length(subs) == 0 -      Enum.each(subs, fn {uuid, _} -> assert String.contains?(text, uuid) end) +      assert expected_text == +               "<span class='h-card'><a data-user='#{user.id}' class='u-url mention' href='#{ +                 user.ap_id +               }'>@<span>#{user.nickname}</span></a></span> hey dude i hate <span class='h-card'><a data-user='#{ +                 third_user.id +               }' class='u-url mention' href='#{third_user.ap_id}'>@<span>#{third_user.nickname}</span></a></span>" +    end -      expected_text = "@a hi" -      assert expected_text == Formatter.finalize({subs, text}) +    test "given the 'safe_mention' option, it will still work without any mention" do +      text = "A post without any mention" +      {expected_text, mentions, [] = _tags} = Formatter.linkify(text, safe_mention: true) + +      assert mentions == [] +      assert expected_text == text      end    end @@ -209,14 +212,14 @@ defmodule Pleroma.FormatterTest do      test "parses tags in the text" do        text = "Here's a #Test. Maybe these are #working or not. What about #漢字? And #は。" -      expected = [ +      expected_tags = [          {"#Test", "test"},          {"#working", "working"}, -        {"#漢字", "漢字"}, -        {"#は", "は"} +        {"#は", "は"}, +        {"#漢字", "漢字"}        ] -      assert Formatter.parse_tags(text) == expected +      assert {_text, [], ^expected_tags} = Formatter.linkify(text)      end    end @@ -230,15 +233,15 @@ defmodule Pleroma.FormatterTest do      archaeme = insert(:user, %{nickname: "archaeme"})      archaeme_remote = insert(:user, %{nickname: "archaeme@archae.me"}) -    expected_result = [ -      {"@gsimg", gsimg}, +    expected_mentions = [        {"@archaeme", archaeme},        {"@archaeme@archae.me", archaeme_remote}, -      {"@o", o}, -      {"@jimm", jimm} +      {"@gsimg", gsimg}, +      {"@jimm", jimm}, +      {"@o", o}      ] -    assert Formatter.parse_mentions(text) == expected_result +    assert {_text, ^expected_mentions, []} = Formatter.linkify(text)    end    test "it adds cool emoji" do @@ -268,7 +271,9 @@ defmodule Pleroma.FormatterTest do    test "it returns the emoji used in the text" do      text = "I love :moominmamma:" -    assert Formatter.get_emoji(text) == [{"moominmamma", "/finmoji/128px/moominmamma-128.png"}] +    assert Formatter.get_emoji(text) == [ +             {"moominmamma", "/finmoji/128px/moominmamma-128.png", "Finmoji"} +           ]    end    test "it returns a nice empty result when no emojis are present" do @@ -281,22 +286,10 @@ defmodule Pleroma.FormatterTest do      assert Formatter.get_emoji(text) == []    end -  describe "/mentions_escape" do -    test "it returns text with escaped mention names" do -      text = """ -      @a_breakin_glass@cybre.space -      (also, little voice inside my head thinking "maybe this will encourage people -      pronouncing it properly instead of saying _raKEWdo_ ") -      """ - -      escape_text = """ -      @a\\_breakin\\_glass@cybre\\.space -      (also, little voice inside my head thinking \"maybe this will encourage people -      pronouncing it properly instead of saying _raKEWdo_ \") -      """ - -      mentions = [{"@a_breakin_glass@cybre.space", %{}}] -      assert Formatter.mentions_escape(text, mentions) == escape_text -    end +  test "it escapes HTML in plain text" do +    text = "hello & world google.com/?a=b&c=d \n http://test.com/?a=b&c=d 1" +    expected = "hello & world google.com/?a=b&c=d \n http://test.com/?a=b&c=d 1" + +    assert Formatter.html_escape(text, "text/plain") == expected    end  end diff --git a/test/html_test.exs b/test/html_test.exs index 29cab17f3..0b5d3d892 100644 --- a/test/html_test.exs +++ b/test/html_test.exs @@ -10,6 +10,8 @@ defmodule Pleroma.HTMLTest do      <b>this is in bold</b>      <p>this is a paragraph</p>      this is a linebreak<br /> +    this is a link with allowed "rel" attribute: <a href="http://example.com/" rel="tag">example.com</a> +    this is a link with not allowed "rel" attribute: <a href="http://example.com/" rel="tag noallowed">example.com</a>      this is an image: <img src="http://example.com/image.jpg"><br />      <script>alert('hacked')</script>    """ @@ -24,6 +26,8 @@ defmodule Pleroma.HTMLTest do        this is in bold          this is a paragraph          this is a linebreak +        this is a link with allowed "rel" attribute: example.com +        this is a link with not allowed "rel" attribute: example.com          this is an image:           alert('hacked')        """ @@ -44,6 +48,8 @@ defmodule Pleroma.HTMLTest do        this is in bold          <p>this is a paragraph</p>          this is a linebreak<br /> +        this is a link with allowed "rel" attribute: <a href="http://example.com/" rel="tag">example.com</a> +        this is a link with not allowed "rel" attribute: <a href="http://example.com/">example.com</a>          this is an image: <img src="http://example.com/image.jpg" /><br />          alert('hacked')        """ @@ -66,6 +72,8 @@ defmodule Pleroma.HTMLTest do        <b>this is in bold</b>          <p>this is a paragraph</p>          this is a linebreak<br /> +        this is a link with allowed "rel" attribute: <a href="http://example.com/" rel="tag">example.com</a> +        this is a link with not allowed "rel" attribute: <a href="http://example.com/">example.com</a>          this is an image: <img src="http://example.com/image.jpg" /><br />          alert('hacked')        """ diff --git a/test/integration/mastodon_websocket_test.exs b/test/integration/mastodon_websocket_test.exs index 2e385f5ad..b42c9ef07 100644 --- a/test/integration/mastodon_websocket_test.exs +++ b/test/integration/mastodon_websocket_test.exs @@ -7,9 +7,9 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do    import Pleroma.Factory +  alias Pleroma.Integration.WebsocketClient    alias Pleroma.Web.CommonAPI    alias Pleroma.Web.OAuth -  alias Pleroma.Integration.WebsocketClient    alias Pleroma.Web.Streamer    @path Pleroma.Web.Endpoint.url() @@ -80,7 +80,7 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do          Pleroma.Repo.insert(            OAuth.App.register_changeset(%OAuth.App{}, %{              client_name: "client", -            scopes: "scope", +            scopes: ["scope"],              redirect_uris: "url"            })          ) diff --git a/test/media_proxy_test.exs b/test/media_proxy_test.exs index 05d927422..ddbadfbf5 100644 --- a/test/media_proxy_test.exs +++ b/test/media_proxy_test.exs @@ -140,6 +140,15 @@ defmodule Pleroma.MediaProxyTest do        assert String.starts_with?(encoded, Pleroma.Config.get([:media_proxy, :base_url]))      end + +    # https://git.pleroma.social/pleroma/pleroma/issues/580 +    test "encoding S3 links (must preserve `%2F`)" do +      url = +        "https://s3.amazonaws.com/example/test.png?X-Amz-Credential=your-access-key-id%2F20130721%2Fus-east-1%2Fs3%2Faws4_request" + +      encoded = url(url) +      assert decode_result(encoded) == url +    end    end    describe "when disabled" do diff --git a/test/notification_test.exs b/test/notification_test.exs index 94fb0ab15..c3db77b6c 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -4,10 +4,11 @@  defmodule Pleroma.NotificationTest do    use Pleroma.DataCase -  alias Pleroma.Web.TwitterAPI.TwitterAPI -  alias Pleroma.Web.CommonAPI -  alias Pleroma.{User, Notification} +  alias Pleroma.Notification +  alias Pleroma.User    alias Pleroma.Web.ActivityPub.Transmogrifier +  alias Pleroma.Web.CommonAPI +  alias Pleroma.Web.TwitterAPI.TwitterAPI    import Pleroma.Factory    describe "create_notifications" do @@ -28,6 +29,18 @@ defmodule Pleroma.NotificationTest do        assert notification.activity_id == activity.id        assert other_notification.activity_id == activity.id      end + +    test "it creates a notification for subscribed users" do +      user = insert(:user) +      subscriber = insert(:user) + +      User.subscribe(subscriber, user) + +      {:ok, status} = TwitterAPI.create_status(user, %{"status" => "Akariiiin"}) +      {:ok, [notification]} = Notification.create_notifications(status) + +      assert notification.user_id == subscriber.id +    end    end    describe "create_notification" do @@ -40,6 +53,75 @@ defmodule Pleroma.NotificationTest do        assert nil == Notification.create_notification(activity, user)      end +    test "it doesn't create a notificatin for the user if the user mutes the activity author" do +      muter = insert(:user) +      muted = insert(:user) +      {:ok, _} = User.mute(muter, muted) +      muter = Repo.get(User, muter.id) +      {:ok, activity} = CommonAPI.post(muted, %{"status" => "Hi @#{muter.nickname}"}) + +      assert nil == Notification.create_notification(activity, muter) +    end + +    test "it doesn't create a notification for an activity from a muted thread" do +      muter = insert(:user) +      other_user = insert(:user) +      {:ok, activity} = CommonAPI.post(muter, %{"status" => "hey"}) +      CommonAPI.add_mute(muter, activity) + +      {:ok, activity} = +        CommonAPI.post(other_user, %{ +          "status" => "Hi @#{muter.nickname}", +          "in_reply_to_status_id" => activity.id +        }) + +      assert nil == Notification.create_notification(activity, muter) +    end + +    test "it disables notifications from people on remote instances" do +      user = insert(:user, info: %{notification_settings: %{"remote" => false}}) +      other_user = insert(:user) + +      create_activity = %{ +        "@context" => "https://www.w3.org/ns/activitystreams", +        "type" => "Create", +        "to" => ["https://www.w3.org/ns/activitystreams#Public"], +        "actor" => other_user.ap_id, +        "object" => %{ +          "type" => "Note", +          "content" => "Hi @#{user.nickname}", +          "attributedTo" => other_user.ap_id +        } +      } + +      {:ok, %{local: false} = activity} = Transmogrifier.handle_incoming(create_activity) +      assert nil == Notification.create_notification(activity, user) +    end + +    test "it disables notifications from people on the local instance" do +      user = insert(:user, info: %{notification_settings: %{"local" => false}}) +      other_user = insert(:user) +      {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"}) +      assert nil == Notification.create_notification(activity, user) +    end + +    test "it disables notifications from followers" do +      follower = insert(:user) +      followed = insert(:user, info: %{notification_settings: %{"followers" => false}}) +      User.follow(follower, followed) +      {:ok, activity} = CommonAPI.post(follower, %{"status" => "hey @#{followed.nickname}"}) +      assert nil == Notification.create_notification(activity, followed) +    end + +    test "it disables notifications from people the user follows" do +      follower = insert(:user, info: %{notification_settings: %{"follows" => false}}) +      followed = insert(:user) +      User.follow(follower, followed) +      follower = Repo.get(User, follower.id) +      {:ok, activity} = CommonAPI.post(followed, %{"status" => "hey @#{follower.nickname}"}) +      assert nil == Notification.create_notification(activity, follower) +    end +      test "it doesn't create a notification for user if he is the activity author" do        activity = insert(:note_activity)        author = User.get_by_ap_id(activity.data["actor"]) @@ -83,6 +165,28 @@ defmodule Pleroma.NotificationTest do        {:ok, dupe} = TwitterAPI.repeat(user, status.id)        assert nil == Notification.create_notification(dupe, retweeted_user)      end + +    test "it doesn't create duplicate notifications for follow+subscribed users" do +      user = insert(:user) +      subscriber = insert(:user) + +      {:ok, _, _, _} = TwitterAPI.follow(subscriber, %{"user_id" => user.id}) +      User.subscribe(subscriber, user) +      {:ok, status} = TwitterAPI.create_status(user, %{"status" => "Akariiiin"}) +      {:ok, [_notif]} = Notification.create_notifications(status) +    end + +    test "it doesn't create subscription notifications if the recipient cannot see the status" do +      user = insert(:user) +      subscriber = insert(:user) + +      User.subscribe(subscriber, user) + +      {:ok, status} = +        TwitterAPI.create_status(user, %{"status" => "inwisible", "visibility" => "direct"}) + +      assert {:ok, []} == Notification.create_notifications(status) +    end    end    describe "get notification" do @@ -299,7 +403,7 @@ defmodule Pleroma.NotificationTest do        {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"}) -      assert length(Notification.for_user(user)) == 0 +      assert Enum.empty?(Notification.for_user(user))        {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) @@ -307,7 +411,7 @@ defmodule Pleroma.NotificationTest do        {:ok, _} = CommonAPI.delete(activity.id, user) -      assert length(Notification.for_user(user)) == 0 +      assert Enum.empty?(Notification.for_user(user))      end      test "liking an activity results in 1 notification, then 0 if the activity is unliked" do @@ -316,7 +420,7 @@ defmodule Pleroma.NotificationTest do        {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"}) -      assert length(Notification.for_user(user)) == 0 +      assert Enum.empty?(Notification.for_user(user))        {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) @@ -324,7 +428,7 @@ defmodule Pleroma.NotificationTest do        {:ok, _, _, _} = CommonAPI.unfavorite(activity.id, other_user) -      assert length(Notification.for_user(user)) == 0 +      assert Enum.empty?(Notification.for_user(user))      end      test "repeating an activity results in 1 notification, then 0 if the activity is deleted" do @@ -333,7 +437,7 @@ defmodule Pleroma.NotificationTest do        {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"}) -      assert length(Notification.for_user(user)) == 0 +      assert Enum.empty?(Notification.for_user(user))        {:ok, _, _} = CommonAPI.repeat(activity.id, other_user) @@ -341,7 +445,7 @@ defmodule Pleroma.NotificationTest do        {:ok, _} = CommonAPI.delete(activity.id, user) -      assert length(Notification.for_user(user)) == 0 +      assert Enum.empty?(Notification.for_user(user))      end      test "repeating an activity results in 1 notification, then 0 if the activity is unrepeated" do @@ -350,7 +454,7 @@ defmodule Pleroma.NotificationTest do        {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"}) -      assert length(Notification.for_user(user)) == 0 +      assert Enum.empty?(Notification.for_user(user))        {:ok, _, _} = CommonAPI.repeat(activity.id, other_user) @@ -358,7 +462,7 @@ defmodule Pleroma.NotificationTest do        {:ok, _, _} = CommonAPI.unrepeat(activity.id, other_user) -      assert length(Notification.for_user(user)) == 0 +      assert Enum.empty?(Notification.for_user(user))      end      test "liking an activity which is already deleted does not generate a notification" do @@ -367,15 +471,15 @@ defmodule Pleroma.NotificationTest do        {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"}) -      assert length(Notification.for_user(user)) == 0 +      assert Enum.empty?(Notification.for_user(user))        {:ok, _deletion_activity} = CommonAPI.delete(activity.id, user) -      assert length(Notification.for_user(user)) == 0 +      assert Enum.empty?(Notification.for_user(user))        {:error, _} = CommonAPI.favorite(activity.id, other_user) -      assert length(Notification.for_user(user)) == 0 +      assert Enum.empty?(Notification.for_user(user))      end      test "repeating an activity which is already deleted does not generate a notification" do @@ -384,15 +488,15 @@ defmodule Pleroma.NotificationTest do        {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"}) -      assert length(Notification.for_user(user)) == 0 +      assert Enum.empty?(Notification.for_user(user))        {:ok, _deletion_activity} = CommonAPI.delete(activity.id, user) -      assert length(Notification.for_user(user)) == 0 +      assert Enum.empty?(Notification.for_user(user))        {:error, _} = CommonAPI.repeat(activity.id, other_user) -      assert length(Notification.for_user(user)) == 0 +      assert Enum.empty?(Notification.for_user(user))      end      test "replying to a deleted post without tagging does not generate a notification" do @@ -408,7 +512,7 @@ defmodule Pleroma.NotificationTest do            "in_reply_to_status_id" => activity.id          }) -      assert length(Notification.for_user(user)) == 0 +      assert Enum.empty?(Notification.for_user(user))      end    end  end diff --git a/test/object_test.exs b/test/object_test.exs index 72194975d..911757d57 100644 --- a/test/object_test.exs +++ b/test/object_test.exs @@ -5,7 +5,8 @@  defmodule Pleroma.ObjectTest do    use Pleroma.DataCase    import Pleroma.Factory -  alias Pleroma.{Repo, Object} +  alias Pleroma.Object +  alias Pleroma.Repo    test "returns an object by it's AP id" do      object = insert(:note) diff --git a/test/plugs/legacy_authentication_plug_test.exs b/test/plugs/legacy_authentication_plug_test.exs index 302662797..8b0b06772 100644 --- a/test/plugs/legacy_authentication_plug_test.exs +++ b/test/plugs/legacy_authentication_plug_test.exs @@ -47,16 +47,18 @@ defmodule Pleroma.Plugs.LegacyAuthenticationPlugTest do        |> assign(:auth_user, user)      conn = -      with_mock User, -        reset_password: fn user, %{password: password, password_confirmation: password} -> -          send(self(), :reset_password) -          {:ok, user} -        end do -        conn -        |> LegacyAuthenticationPlug.call(%{}) +      with_mocks([ +        {:crypt, [], [crypt: fn _password, password_hash -> password_hash end]}, +        {User, [], +         [ +           reset_password: fn user, %{password: password, password_confirmation: password} -> +             {:ok, user} +           end +         ]} +      ]) do +        LegacyAuthenticationPlug.call(conn, %{})        end -    assert_received :reset_password      assert conn.assigns.user == user    end diff --git a/test/plugs/oauth_scopes_plug_test.exs b/test/plugs/oauth_scopes_plug_test.exs new file mode 100644 index 000000000..f328026df --- /dev/null +++ b/test/plugs/oauth_scopes_plug_test.exs @@ -0,0 +1,122 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Plugs.OAuthScopesPlugTest do +  use Pleroma.Web.ConnCase, async: true + +  alias Pleroma.Plugs.OAuthScopesPlug +  alias Pleroma.Repo + +  import Pleroma.Factory + +  test "proceeds with no op if `assigns[:token]` is nil", %{conn: conn} do +    conn = +      conn +      |> assign(:user, insert(:user)) +      |> OAuthScopesPlug.call(%{scopes: ["read"]}) + +    refute conn.halted +    assert conn.assigns[:user] +  end + +  test "proceeds with no op if `token.scopes` fulfill specified 'any of' conditions", %{ +    conn: conn +  } do +    token = insert(:oauth_token, scopes: ["read", "write"]) |> Repo.preload(:user) + +    conn = +      conn +      |> assign(:user, token.user) +      |> assign(:token, token) +      |> OAuthScopesPlug.call(%{scopes: ["read"]}) + +    refute conn.halted +    assert conn.assigns[:user] +  end + +  test "proceeds with no op if `token.scopes` fulfill specified 'all of' conditions", %{ +    conn: conn +  } do +    token = insert(:oauth_token, scopes: ["scope1", "scope2", "scope3"]) |> Repo.preload(:user) + +    conn = +      conn +      |> assign(:user, token.user) +      |> assign(:token, token) +      |> OAuthScopesPlug.call(%{scopes: ["scope2", "scope3"], op: :&}) + +    refute conn.halted +    assert conn.assigns[:user] +  end + +  test "proceeds with cleared `assigns[:user]` if `token.scopes` doesn't fulfill specified 'any of' conditions " <> +         "and `fallback: :proceed_unauthenticated` option is specified", +       %{conn: conn} do +    token = insert(:oauth_token, scopes: ["read", "write"]) |> Repo.preload(:user) + +    conn = +      conn +      |> assign(:user, token.user) +      |> assign(:token, token) +      |> OAuthScopesPlug.call(%{scopes: ["follow"], fallback: :proceed_unauthenticated}) + +    refute conn.halted +    refute conn.assigns[:user] +  end + +  test "proceeds with cleared `assigns[:user]` if `token.scopes` doesn't fulfill specified 'all of' conditions " <> +         "and `fallback: :proceed_unauthenticated` option is specified", +       %{conn: conn} do +    token = insert(:oauth_token, scopes: ["read", "write"]) |> Repo.preload(:user) + +    conn = +      conn +      |> assign(:user, token.user) +      |> assign(:token, token) +      |> OAuthScopesPlug.call(%{ +        scopes: ["read", "follow"], +        op: :&, +        fallback: :proceed_unauthenticated +      }) + +    refute conn.halted +    refute conn.assigns[:user] +  end + +  test "returns 403 and halts in case of no :fallback option and `token.scopes` not fulfilling specified 'any of' conditions", +       %{conn: conn} do +    token = insert(:oauth_token, scopes: ["read", "write"]) +    any_of_scopes = ["follow"] + +    conn = +      conn +      |> assign(:token, token) +      |> OAuthScopesPlug.call(%{scopes: any_of_scopes}) + +    assert conn.halted +    assert 403 == conn.status + +    expected_error = "Insufficient permissions: #{Enum.join(any_of_scopes, ", ")}." +    assert Jason.encode!(%{error: expected_error}) == conn.resp_body +  end + +  test "returns 403 and halts in case of no :fallback option and `token.scopes` not fulfilling specified 'all of' conditions", +       %{conn: conn} do +    token = insert(:oauth_token, scopes: ["read", "write"]) +    all_of_scopes = ["write", "follow"] + +    conn = +      conn +      |> assign(:token, token) +      |> OAuthScopesPlug.call(%{scopes: all_of_scopes, op: :&}) + +    assert conn.halted +    assert 403 == conn.status + +    expected_error = +      "Insufficient permissions: #{Enum.join(all_of_scopes -- token.scopes, ", ")}." + +    assert Jason.encode!(%{error: expected_error}) == conn.resp_body +  end +end diff --git a/test/plugs/uploaded_media_plug_test.exs b/test/plugs/uploaded_media_plug_test.exs new file mode 100644 index 000000000..49cf5396a --- /dev/null +++ b/test/plugs/uploaded_media_plug_test.exs @@ -0,0 +1,43 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.UploadedMediaPlugTest do +  use Pleroma.Web.ConnCase +  alias Pleroma.Upload + +  defp upload_file(context) do +    Pleroma.DataCase.ensure_local_uploader(context) +    File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg") + +    file = %Plug.Upload{ +      content_type: "image/jpg", +      path: Path.absname("test/fixtures/image_tmp.jpg"), +      filename: "nice_tf.jpg" +    } + +    {:ok, data} = Upload.store(file) +    [%{"href" => attachment_url} | _] = data["url"] +    [attachment_url: attachment_url] +  end + +  setup_all :upload_file + +  test "does not send Content-Disposition header when name param is not set", %{ +    attachment_url: attachment_url +  } do +    conn = get(build_conn(), attachment_url) +    refute Enum.any?(conn.resp_headers, &(elem(&1, 0) == "content-disposition")) +  end + +  test "sends Content-Disposition header when name param is set", %{ +    attachment_url: attachment_url +  } do +    conn = get(build_conn(), attachment_url <> "?name=\"cofe\".gif") + +    assert Enum.any?( +             conn.resp_headers, +             &(&1 == {"content-disposition", "filename=\"\\\"cofe\\\".gif\""}) +           ) +  end +end diff --git a/test/registration_test.exs b/test/registration_test.exs new file mode 100644 index 000000000..6143b82c7 --- /dev/null +++ b/test/registration_test.exs @@ -0,0 +1,59 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.RegistrationTest do +  use Pleroma.DataCase + +  import Pleroma.Factory + +  alias Pleroma.Registration +  alias Pleroma.Repo + +  describe "generic changeset" do +    test "requires :provider, :uid" do +      registration = build(:registration, provider: nil, uid: nil) + +      cs = Registration.changeset(registration, %{}) +      refute cs.valid? + +      assert [ +               provider: {"can't be blank", [validation: :required]}, +               uid: {"can't be blank", [validation: :required]} +             ] == cs.errors +    end + +    test "ensures uniqueness of [:provider, :uid]" do +      registration = insert(:registration) +      registration2 = build(:registration, provider: registration.provider, uid: registration.uid) + +      cs = Registration.changeset(registration2, %{}) +      assert cs.valid? + +      assert {:error, +              %Ecto.Changeset{ +                errors: [ +                  uid: +                    {"has already been taken", +                     [constraint: :unique, constraint_name: "registrations_provider_uid_index"]} +                ] +              }} = Repo.insert(cs) + +      # Note: multiple :uid values per [:user_id, :provider] are intentionally allowed +      cs2 = Registration.changeset(registration2, %{uid: "available.uid"}) +      assert cs2.valid? +      assert {:ok, _} = Repo.insert(cs2) + +      cs3 = Registration.changeset(registration2, %{provider: "provider2"}) +      assert cs3.valid? +      assert {:ok, _} = Repo.insert(cs3) +    end + +    test "allows `nil` :user_id (user-unbound registration)" do +      registration = build(:registration, user_id: nil) +      cs = Registration.changeset(registration, %{}) +      assert cs.valid? +      assert {:ok, _} = Repo.insert(cs) +    end +  end +end diff --git a/test/scheduled_activity_test.exs b/test/scheduled_activity_test.exs new file mode 100644 index 000000000..edc7cc3f9 --- /dev/null +++ b/test/scheduled_activity_test.exs @@ -0,0 +1,64 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.ScheduledActivityTest do +  use Pleroma.DataCase +  alias Pleroma.DataCase +  alias Pleroma.ScheduledActivity +  import Pleroma.Factory + +  setup context do +    DataCase.ensure_local_uploader(context) +  end + +  describe "creation" do +    test "when daily user limit is exceeded" do +      user = insert(:user) + +      today = +        NaiveDateTime.utc_now() +        |> NaiveDateTime.add(:timer.minutes(6), :millisecond) +        |> NaiveDateTime.to_iso8601() + +      attrs = %{params: %{}, scheduled_at: today} +      {:ok, _} = ScheduledActivity.create(user, attrs) +      {:ok, _} = ScheduledActivity.create(user, attrs) +      {:error, changeset} = ScheduledActivity.create(user, attrs) +      assert changeset.errors == [scheduled_at: {"daily limit exceeded", []}] +    end + +    test "when total user limit is exceeded" do +      user = insert(:user) + +      today = +        NaiveDateTime.utc_now() +        |> NaiveDateTime.add(:timer.minutes(6), :millisecond) +        |> NaiveDateTime.to_iso8601() + +      tomorrow = +        NaiveDateTime.utc_now() +        |> NaiveDateTime.add(:timer.hours(36), :millisecond) +        |> NaiveDateTime.to_iso8601() + +      {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: today}) +      {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: today}) +      {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow}) +      {:error, changeset} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow}) +      assert changeset.errors == [scheduled_at: {"total limit exceeded", []}] +    end + +    test "when scheduled_at is earlier than 5 minute from now" do +      user = insert(:user) + +      scheduled_at = +        NaiveDateTime.utc_now() +        |> NaiveDateTime.add(:timer.minutes(4), :millisecond) +        |> NaiveDateTime.to_iso8601() + +      attrs = %{params: %{}, scheduled_at: scheduled_at} +      {:error, changeset} = ScheduledActivity.create(user, attrs) +      assert changeset.errors == [scheduled_at: {"must be at least 5 minutes from now", []}] +    end +  end +end diff --git a/test/scheduled_activity_worker_test.exs b/test/scheduled_activity_worker_test.exs new file mode 100644 index 000000000..b9c91dda6 --- /dev/null +++ b/test/scheduled_activity_worker_test.exs @@ -0,0 +1,19 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.ScheduledActivityWorkerTest do +  use Pleroma.DataCase +  alias Pleroma.ScheduledActivity +  import Pleroma.Factory + +  test "creates a status from the scheduled activity" do +    user = insert(:user) +    scheduled_activity = insert(:scheduled_activity, user: user, params: %{status: "hi"}) +    Pleroma.ScheduledActivityWorker.perform(:execute, scheduled_activity.id) + +    refute Repo.get(ScheduledActivity, scheduled_activity.id) +    activity = Repo.all(Pleroma.Activity) |> Enum.find(&(&1.actor == user.ap_id)) +    assert activity.data["object"]["content"] == "hi" +  end +end diff --git a/test/support/builders/user_builder.ex b/test/support/builders/user_builder.ex index 7a1ca79b5..f58e1b0ad 100644 --- a/test/support/builders/user_builder.ex +++ b/test/support/builders/user_builder.ex @@ -1,5 +1,6 @@  defmodule Pleroma.Builders.UserBuilder do -  alias Pleroma.{User, Repo} +  alias Pleroma.Repo +  alias Pleroma.User    def build(data \\ %{}) do      user = %User{ diff --git a/test/support/captcha_mock.ex b/test/support/captcha_mock.ex index 9061f2b45..ef4e68bc5 100644 --- a/test/support/captcha_mock.ex +++ b/test/support/captcha_mock.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Captcha.Mock do    @behaviour Service    @impl Service -  def new(), do: %{type: :mock} +  def new, do: %{type: :mock}    @impl Service    def validate(_token, _captcha, _data), do: :ok diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex index c201d9a9b..ec5892ff5 100644 --- a/test/support/conn_case.ex +++ b/test/support/conn_case.ex @@ -33,6 +33,7 @@ defmodule Pleroma.Web.ConnCase do    setup tags do      Cachex.clear(:user_cache) +    Cachex.clear(:object_cache)      :ok = Ecto.Adapters.SQL.Sandbox.checkout(Pleroma.Repo)      unless tags[:async] do diff --git a/test/support/data_case.ex b/test/support/data_case.ex index 56d5896ad..df260bd3f 100644 --- a/test/support/data_case.ex +++ b/test/support/data_case.ex @@ -32,6 +32,7 @@ defmodule Pleroma.DataCase do    setup tags do      Cachex.clear(:user_cache) +    Cachex.clear(:object_cache)      :ok = Ecto.Adapters.SQL.Sandbox.checkout(Pleroma.Repo)      unless tags[:async] do diff --git a/test/support/factory.ex b/test/support/factory.ex index 4ac77981a..ea59912cf 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -23,7 +23,7 @@ defmodule Pleroma.Factory do      }    end -  def note_factory do +  def note_factory(attrs \\ %{}) do      text = sequence(:text, &"This is :moominmamma: note #{&1}")      user = insert(:user) @@ -46,7 +46,7 @@ defmodule Pleroma.Factory do      }      %Pleroma.Object{ -      data: data +      data: merge_attributes(data, Map.get(attrs, :data, %{}))      }    end @@ -95,8 +95,8 @@ defmodule Pleroma.Factory do      }    end -  def note_activity_factory do -    note = insert(:note) +  def note_activity_factory(attrs \\ %{}) do +    note = attrs[:note] || insert(:note)      data = %{        "id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(), @@ -135,9 +135,9 @@ defmodule Pleroma.Factory do      }    end -  def announce_activity_factory do -    note_activity = insert(:note_activity) -    user = insert(:user) +  def announce_activity_factory(attrs \\ %{}) do +    note_activity = attrs[:note_activity] || insert(:note_activity) +    user = attrs[:user] || insert(:user)      data = %{        "type" => "Announce", @@ -193,7 +193,7 @@ defmodule Pleroma.Factory do    def websub_subscription_factory do      %Pleroma.Web.Websub.WebsubServerSubscription{        topic: "http://example.org", -      callback: "http://example/org/callback", +      callback: "http://example.org/callback",        secret: "here's a secret",        valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), 100),        state: "requested" @@ -214,10 +214,81 @@ defmodule Pleroma.Factory do      %Pleroma.Web.OAuth.App{        client_name: "Some client",        redirect_uris: "https://example.com/callback", -      scopes: "read", +      scopes: ["read", "write", "follow", "push"],        website: "https://example.com", -      client_id: "aaabbb==", +      client_id: Ecto.UUID.generate(),        client_secret: "aaa;/&bbb"      }    end + +  def instance_factory do +    %Pleroma.Instances.Instance{ +      host: "domain.com", +      unreachable_since: nil +    } +  end + +  def oauth_token_factory do +    oauth_app = insert(:oauth_app) + +    %Pleroma.Web.OAuth.Token{ +      token: :crypto.strong_rand_bytes(32) |> Base.url_encode64(), +      refresh_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64(), +      user: build(:user), +      app_id: oauth_app.id, +      valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10) +    } +  end + +  def oauth_authorization_factory do +    %Pleroma.Web.OAuth.Authorization{ +      token: :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false), +      scopes: ["read", "write", "follow", "push"], +      valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10), +      user: build(:user), +      app: build(:oauth_app) +    } +  end + +  def push_subscription_factory do +    %Pleroma.Web.Push.Subscription{ +      user: build(:user), +      token: build(:oauth_token), +      endpoint: "https://example.com/example/1234", +      key_auth: "8eDyX_uCN0XRhSbY5hs7Hg==", +      key_p256dh: +        "BCIWgsnyXDv1VkhqL2P7YRBvdeuDnlwAPT2guNhdIoW3IP7GmHh1SMKPLxRf7x8vJy6ZFK3ol2ohgn_-0yP7QQA=", +      data: %{} +    } +  end + +  def notification_factory do +    %Pleroma.Notification{ +      user: build(:user) +    } +  end + +  def scheduled_activity_factory do +    %Pleroma.ScheduledActivity{ +      user: build(:user), +      scheduled_at: NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(60), :millisecond), +      params: build(:note) |> Map.from_struct() |> Map.get(:data) +    } +  end + +  def registration_factory do +    user = insert(:user) + +    %Pleroma.Registration{ +      user: user, +      provider: "twitter", +      uid: "171799000", +      info: %{ +        "name" => "John Doe", +        "email" => "john@doe.com", +        "nickname" => "johndoe", +        "description" => "My bio" +      } +    } +  end  end diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex index 3043d2be6..5b355bfe6 100644 --- a/test/support/http_request_mock.ex +++ b/test/support/http_request_mock.ex @@ -36,6 +36,43 @@ defmodule HttpRequestMock do       }}    end +  def get("https://mastodon.social/users/emelie/statuses/101849165031453009", _, _, _) do +    {:ok, +     %Tesla.Env{ +       status: 200, +       body: File.read!("test/fixtures/httpoison_mock/status.emelie.json") +     }} +  end + +  def get("https://mastodon.social/users/emelie", _, _, _) do +    {:ok, +     %Tesla.Env{ +       status: 200, +       body: File.read!("test/fixtures/httpoison_mock/emelie.json") +     }} +  end + +  def get( +        "https://mastodon.social/.well-known/webfinger?resource=https://mastodon.social/users/emelie", +        _, +        _, +        _ +      ) do +    {:ok, +     %Tesla.Env{ +       status: 200, +       body: File.read!("test/fixtures/httpoison_mock/webfinger_emelie.json") +     }} +  end + +  def get("https://mastodon.social/users/emelie.atom", _, _, _) do +    {:ok, +     %Tesla.Env{ +       status: 200, +       body: File.read!("test/fixtures/httpoison_mock/emelie.atom") +     }} +  end +    def get(          "https://osada.macgirvin.com/.well-known/webfinger?resource=acct:mike@osada.macgirvin.com",          _, @@ -143,7 +180,10 @@ defmodule HttpRequestMock do       }}    end -  def get("https://squeet.me/xrd/?uri=lain@squeet.me", _, _, +  def get( +        "https://squeet.me/xrd/?uri=lain@squeet.me", +        _, +        _,          Accept: "application/xrd+xml,application/jrd+json"        ) do      {:ok, @@ -153,7 +193,10 @@ defmodule HttpRequestMock do       }}    end -  def get("https://mst3k.interlinked.me/users/luciferMysticus", _, _, +  def get( +        "https://mst3k.interlinked.me/users/luciferMysticus", +        _, +        _,          Accept: "application/activity+json"        ) do      {:ok, @@ -171,7 +214,10 @@ defmodule HttpRequestMock do       }}    end -  def get("https://hubzilla.example.org/channel/kaniini", _, _, +  def get( +        "https://hubzilla.example.org/channel/kaniini", +        _, +        _,          Accept: "application/activity+json"        ) do      {:ok, @@ -248,7 +294,10 @@ defmodule HttpRequestMock do       }}    end -  def get("http://mastodon.example.org/@admin/99541947525187367", _, _, +  def get( +        "http://mastodon.example.org/@admin/99541947525187367", +        _, +        _,          Accept: "application/activity+json"        ) do      {:ok, @@ -274,7 +323,10 @@ defmodule HttpRequestMock do       }}    end -  def get("https://mstdn.io/users/mayuutann/statuses/99568293732299394", _, _, +  def get( +        "https://mstdn.io/users/mayuutann/statuses/99568293732299394", +        _, +        _,          Accept: "application/activity+json"        ) do      {:ok, @@ -429,7 +481,10 @@ defmodule HttpRequestMock do       }}    end -  def get("https://social.sakamoto.gq/objects/0ccc1a2c-66b0-4305-b23a-7f7f2b040056", _, _, +  def get( +        "https://social.sakamoto.gq/objects/0ccc1a2c-66b0-4305-b23a-7f7f2b040056", +        _, +        _,          Accept: "application/atom+xml"        ) do      {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/httpoison_mock/sakamoto.atom")}} @@ -510,7 +565,10 @@ defmodule HttpRequestMock do       %Tesla.Env{status: 200, body: File.read!("test/fixtures/httpoison_mock/squeet.me_host_meta")}}    end -  def get("https://squeet.me/xrd?uri=lain@squeet.me", _, _, +  def get( +        "https://squeet.me/xrd?uri=lain@squeet.me", +        _, +        _,          Accept: "application/xrd+xml,application/jrd+json"        ) do      {:ok, @@ -541,7 +599,10 @@ defmodule HttpRequestMock do       }}    end -  def get("http://framatube.org/main/xrd?uri=framasoft@framatube.org", _, _, +  def get( +        "http://framatube.org/main/xrd?uri=framasoft@framatube.org", +        _, +        _,          Accept: "application/xrd+xml,application/jrd+json"        ) do      {:ok, @@ -560,7 +621,10 @@ defmodule HttpRequestMock do       }}    end -  def get("http://gnusocial.de/main/xrd?uri=winterdienst@gnusocial.de", _, _, +  def get( +        "http://gnusocial.de/main/xrd?uri=winterdienst@gnusocial.de", +        _, +        _,          Accept: "application/xrd+xml,application/jrd+json"        ) do      {:ok, @@ -594,7 +658,10 @@ defmodule HttpRequestMock do       }}    end -  def get("https://gerzilla.de/xrd/?uri=kaniini@gerzilla.de", _, _, +  def get( +        "https://gerzilla.de/xrd/?uri=kaniini@gerzilla.de", +        _, +        _,          Accept: "application/xrd+xml,application/jrd+json"        ) do      {:ok, @@ -649,6 +716,10 @@ defmodule HttpRequestMock do      {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/lambadalambda.atom")}}    end +  def get("https://mastodon.social/users/lambadalambda", _, _, _) do +    {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/lambadalambda.json")}} +  end +    def get("https://social.heldscal.la/user/23211", _, _, Accept: "application/activity+json") do      {:ok, Tesla.Mock.json(%{"id" => "https://social.heldscal.la/user/23211"}, status: 200)}    end @@ -657,10 +728,23 @@ defmodule HttpRequestMock do      {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/ogp.html")}}    end +  def get("http://example.com/malformed", _, _, _) do +    {:ok, +     %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/malformed-data.html")}} +  end +    def get("http://example.com/empty", _, _, _) do      {:ok, %Tesla.Env{status: 200, body: "hello"}}    end +  def get("http://404.site" <> _, _, _, _) do +    {:ok, +     %Tesla.Env{ +       status: 404, +       body: "" +     }} +  end +    def get(url, query, body, headers) do      {:error,       "Not implemented the mock response for get #{inspect(url)}, #{query}, #{inspect(body)}, #{ @@ -681,6 +765,26 @@ defmodule HttpRequestMock do       }}    end +  def post("http://200.site" <> _, _, _, _) do +    {:ok, +     %Tesla.Env{ +       status: 200, +       body: "" +     }} +  end + +  def post("http://connrefused.site" <> _, _, _, _) do +    {:error, :connrefused} +  end + +  def post("http://404.site" <> _, _, _, _) do +    {:ok, +     %Tesla.Env{ +       status: 404, +       body: "" +     }} +  end +    def post(url, _query, _body, _headers) do      {:error, "Not implemented the mock response for post #{inspect(url)}"}    end diff --git a/test/support/web_push_http_client_mock.ex b/test/support/web_push_http_client_mock.ex new file mode 100644 index 000000000..d8accd21c --- /dev/null +++ b/test/support/web_push_http_client_mock.ex @@ -0,0 +1,23 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.WebPushHttpClientMock do +  def get(url, headers \\ [], options \\ []) do +    { +      res, +      %Tesla.Env{status: status} +    } = Pleroma.HTTP.request(:get, url, "", headers, options) + +    {res, %{status_code: status}} +  end + +  def post(url, body, headers \\ [], options \\ []) do +    { +      res, +      %Tesla.Env{status: status} +    } = Pleroma.HTTP.request(:post, url, body, headers, options) + +    {res, %{status_code: status}} +  end +end diff --git a/test/tasks/relay_test.exs b/test/tasks/relay_test.exs index 96fac4811..535dc3756 100644 --- a/test/tasks/relay_test.exs +++ b/test/tasks/relay_test.exs @@ -4,8 +4,10 @@  defmodule Mix.Tasks.Pleroma.RelayTest do    alias Pleroma.Activity -  alias Pleroma.Web.ActivityPub.{ActivityPub, Relay, Utils}    alias Pleroma.User +  alias Pleroma.Web.ActivityPub.ActivityPub +  alias Pleroma.Web.ActivityPub.Relay +  alias Pleroma.Web.ActivityPub.Utils    use Pleroma.DataCase    setup_all do @@ -58,7 +60,8 @@ defmodule Mix.Tasks.Pleroma.RelayTest do          ActivityPub.fetch_activities([], %{            "type" => "Undo",            "actor_id" => follower_id, -          "limit" => 1 +          "limit" => 1, +          "skip_preload" => true          })        assert undo_activity.data["type"] == "Undo" diff --git a/test/tasks/user_test.exs b/test/tasks/user_test.exs index 44271898c..242265da5 100644 --- a/test/tasks/user_test.exs +++ b/test/tasks/user_test.exs @@ -151,7 +151,7 @@ defmodule Mix.Tasks.Pleroma.UserTest do        assert message =~ "Successfully unsubscribed"        user = User.get_by_nickname(user.nickname) -      assert length(user.following) == 0 +      assert Enum.empty?(user.following)        assert user.info.deactivated      end @@ -245,7 +245,97 @@ defmodule Mix.Tasks.Pleroma.UserTest do               end) =~ "http"        assert_received {:mix_shell, :info, [message]} -      assert message =~ "Generated" +      assert message =~ "Generated user invite token one time" +    end + +    test "token is generated with expires_at" do +      assert capture_io(fn -> +               Mix.Tasks.Pleroma.User.run([ +                 "invite", +                 "--expires-at", +                 Date.to_string(Date.utc_today()) +               ]) +             end) + +      assert_received {:mix_shell, :info, [message]} +      assert message =~ "Generated user invite token date limited" +    end + +    test "token is generated with max use" do +      assert capture_io(fn -> +               Mix.Tasks.Pleroma.User.run([ +                 "invite", +                 "--max-use", +                 "5" +               ]) +             end) + +      assert_received {:mix_shell, :info, [message]} +      assert message =~ "Generated user invite token reusable" +    end + +    test "token is generated with max use and expires date" do +      assert capture_io(fn -> +               Mix.Tasks.Pleroma.User.run([ +                 "invite", +                 "--max-use", +                 "5", +                 "--expires-at", +                 Date.to_string(Date.utc_today()) +               ]) +             end) + +      assert_received {:mix_shell, :info, [message]} +      assert message =~ "Generated user invite token reusable date limited" +    end +  end + +  describe "running invites" do +    test "invites are listed" do +      {:ok, invite} = Pleroma.UserInviteToken.create_invite() + +      {:ok, invite2} = +        Pleroma.UserInviteToken.create_invite(%{expires_at: Date.utc_today(), max_use: 15}) + +      # assert capture_io(fn -> +      Mix.Tasks.Pleroma.User.run([ +        "invites" +      ]) + +      #  end) + +      assert_received {:mix_shell, :info, [message]} +      assert_received {:mix_shell, :info, [message2]} +      assert_received {:mix_shell, :info, [message3]} +      assert message =~ "Invites list:" +      assert message2 =~ invite.invite_type +      assert message3 =~ invite2.invite_type +    end +  end + +  describe "running revoke_invite" do +    test "invite is revoked" do +      {:ok, invite} = Pleroma.UserInviteToken.create_invite(%{expires_at: Date.utc_today()}) + +      assert capture_io(fn -> +               Mix.Tasks.Pleroma.User.run([ +                 "revoke_invite", +                 invite.token +               ]) +             end) + +      assert_received {:mix_shell, :info, [message]} +      assert message =~ "Invite for token #{invite.token} was revoked." +    end +  end + +  describe "running delete_activities" do +    test "activities are deleted" do +      %{nickname: nickname} = insert(:user) + +      assert :ok == Mix.Tasks.Pleroma.User.run(["delete_activities", nickname]) +      assert_received {:mix_shell, :info, [message]} +      assert message == "User #{nickname} statuses deleted."      end    end  end diff --git a/test/upload_test.exs b/test/upload_test.exs index b2d9eca38..946ebcb5a 100644 --- a/test/upload_test.exs +++ b/test/upload_test.exs @@ -56,7 +56,7 @@ defmodule Pleroma.UploadTest do        assert List.first(data["url"])["href"] ==                 Pleroma.Web.base_url() <> -                 "/media/e7a6d0cf595bff76f14c9a98b6c199539559e8b844e02e51e5efcfd1f614a2df.jpg" +                 "/media/e30397b58d226d6583ab5b8b3c5defb0c682bda5c31ef07a9f57c1c4986e3781.jpg"      end      test "copies the file to the configured folder without deduping" do @@ -153,19 +153,20 @@ defmodule Pleroma.UploadTest do        assert Path.basename(attachment_url["href"]) == "an%E2%80%A6%20image.jpg"      end -    test "replaces : (colon) and ? (question-mark) to %3A and %3F (respectively)" do +    test "escapes reserved uri characters" do        File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")        file = %Plug.Upload{          content_type: "image/jpg",          path: Path.absname("test/fixtures/image_tmp.jpg"), -        filename: "is:an?image.jpg" +        filename: ":?#[]@!$&\\'()*+,;=.jpg"        }        {:ok, data} = Upload.store(file)        [attachment_url | _] = data["url"] -      assert Path.basename(attachment_url["href"]) == "is%3Aan%3Fimage.jpg" +      assert Path.basename(attachment_url["href"]) == +               "%3A%3F%23%5B%5D%40%21%24%26%5C%27%28%29%2A%2B%2C%3B%3D.jpg"      end    end  end diff --git a/test/user_invite_token_test.exs b/test/user_invite_token_test.exs new file mode 100644 index 000000000..276788254 --- /dev/null +++ b/test/user_invite_token_test.exs @@ -0,0 +1,96 @@ +defmodule Pleroma.UserInviteTokenTest do +  use ExUnit.Case, async: true +  use Pleroma.DataCase +  alias Pleroma.UserInviteToken + +  describe "valid_invite?/1 one time invites" do +    setup do +      invite = %UserInviteToken{invite_type: "one_time"} + +      {:ok, invite: invite} +    end + +    test "not used returns true", %{invite: invite} do +      invite = %{invite | used: false} +      assert UserInviteToken.valid_invite?(invite) +    end + +    test "used  returns false", %{invite: invite} do +      invite = %{invite | used: true} +      refute UserInviteToken.valid_invite?(invite) +    end +  end + +  describe "valid_invite?/1 reusable invites" do +    setup do +      invite = %UserInviteToken{ +        invite_type: "reusable", +        max_use: 5 +      } + +      {:ok, invite: invite} +    end + +    test "with less uses then max use returns true", %{invite: invite} do +      invite = %{invite | uses: 4} +      assert UserInviteToken.valid_invite?(invite) +    end + +    test "with equal or more uses then max use returns false", %{invite: invite} do +      invite = %{invite | uses: 5} + +      refute UserInviteToken.valid_invite?(invite) + +      invite = %{invite | uses: 6} + +      refute UserInviteToken.valid_invite?(invite) +    end +  end + +  describe "valid_token?/1 date limited invites" do +    setup do +      invite = %UserInviteToken{invite_type: "date_limited"} +      {:ok, invite: invite} +    end + +    test "expires today returns true", %{invite: invite} do +      invite = %{invite | expires_at: Date.utc_today()} +      assert UserInviteToken.valid_invite?(invite) +    end + +    test "expires yesterday returns false", %{invite: invite} do +      invite = %{invite | expires_at: Date.add(Date.utc_today(), -1)} +      invite = Repo.insert!(invite) +      refute UserInviteToken.valid_invite?(invite) +    end +  end + +  describe "valid_token?/1 reusable date limited invites" do +    setup do +      invite = %UserInviteToken{invite_type: "reusable_date_limited", max_use: 5} +      {:ok, invite: invite} +    end + +    test "not overdue date and less uses returns true", %{invite: invite} do +      invite = %{invite | expires_at: Date.utc_today(), uses: 4} +      assert UserInviteToken.valid_invite?(invite) +    end + +    test "overdue date and less uses returns false", %{invite: invite} do +      invite = %{invite | expires_at: Date.add(Date.utc_today(), -1)} +      invite = Repo.insert!(invite) +      refute UserInviteToken.valid_invite?(invite) +    end + +    test "not overdue date with more uses returns false", %{invite: invite} do +      invite = %{invite | expires_at: Date.utc_today(), uses: 5} +      refute UserInviteToken.valid_invite?(invite) +    end + +    test "overdue date with more uses returns false", %{invite: invite} do +      invite = %{invite | expires_at: Date.add(Date.utc_today(), -1), uses: 5} +      invite = Repo.insert!(invite) +      refute UserInviteToken.valid_invite?(invite) +    end +  end +end diff --git a/test/user_test.exs b/test/user_test.exs index a0657c7b6..d2167a970 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -3,9 +3,12 @@  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.UserTest do +  alias Pleroma.Activity    alias Pleroma.Builders.UserBuilder -  alias Pleroma.{User, Repo, Activity} +  alias Pleroma.Repo +  alias Pleroma.User    alias Pleroma.Web.CommonAPI +    use Pleroma.DataCase    import Pleroma.Factory @@ -48,15 +51,69 @@ defmodule Pleroma.UserTest do      assert expected_followers_collection == User.ap_followers(user)    end +  test "returns all pending follow requests" do +    unlocked = insert(:user) +    locked = insert(:user, %{info: %{locked: true}}) +    follower = insert(:user) + +    Pleroma.Web.TwitterAPI.TwitterAPI.follow(follower, %{"user_id" => unlocked.id}) +    Pleroma.Web.TwitterAPI.TwitterAPI.follow(follower, %{"user_id" => locked.id}) + +    assert {:ok, []} = User.get_follow_requests(unlocked) +    assert {:ok, [activity]} = User.get_follow_requests(locked) + +    assert activity +  end + +  test "doesn't return already accepted or duplicate follow requests" do +    locked = insert(:user, %{info: %{locked: true}}) +    pending_follower = insert(:user) +    accepted_follower = insert(:user) + +    Pleroma.Web.TwitterAPI.TwitterAPI.follow(pending_follower, %{"user_id" => locked.id}) +    Pleroma.Web.TwitterAPI.TwitterAPI.follow(pending_follower, %{"user_id" => locked.id}) +    Pleroma.Web.TwitterAPI.TwitterAPI.follow(accepted_follower, %{"user_id" => locked.id}) +    User.maybe_follow(accepted_follower, locked) + +    assert {:ok, [activity]} = User.get_follow_requests(locked) +    assert activity +  end +    test "follow_all follows mutliple users" do      user = insert(:user) +    followed_zero = insert(:user)      followed_one = insert(:user)      followed_two = insert(:user) +    blocked = insert(:user) +    not_followed = insert(:user) +    reverse_blocked = insert(:user) -    {:ok, user} = User.follow_all(user, [followed_one, followed_two]) +    {:ok, user} = User.block(user, blocked) +    {:ok, reverse_blocked} = User.block(reverse_blocked, user) + +    {:ok, user} = User.follow(user, followed_zero) + +    {:ok, user} = User.follow_all(user, [followed_one, followed_two, blocked, reverse_blocked])      assert User.following?(user, followed_one)      assert User.following?(user, followed_two) +    assert User.following?(user, followed_zero) +    refute User.following?(user, not_followed) +    refute User.following?(user, blocked) +    refute User.following?(user, reverse_blocked) +  end + +  test "follow_all follows mutliple users without duplicating" do +    user = insert(:user) +    followed_zero = insert(:user) +    followed_one = insert(:user) +    followed_two = insert(:user) + +    {:ok, user} = User.follow_all(user, [followed_zero, followed_one]) +    assert length(user.following) == 3 + +    {:ok, user} = User.follow_all(user, [followed_one, followed_two]) +    assert length(user.following) == 4    end    test "follow takes a user and another user" do @@ -65,7 +122,7 @@ defmodule Pleroma.UserTest do      {:ok, user} = User.follow(user, followed) -    user = Repo.get(User, user.id) +    user = User.get_by_id(user.id)      followed = User.get_by_ap_id(followed.ap_id)      assert followed.info.follower_count == 1 @@ -89,6 +146,15 @@ defmodule Pleroma.UserTest do      {:error, _} = User.follow(blockee, blocker)    end +  test "can't subscribe to a user who blocked us" do +    blocker = insert(:user) +    blocked = insert(:user) + +    {:ok, blocker} = User.block(blocker, blocked) + +    {:error, _} = User.subscribe(blocked, blocker) +  end +    test "local users do not automatically follow local locked accounts" do      follower = insert(:user, info: %{locked: true})      followed = insert(:user, info: %{locked: true}) @@ -121,7 +187,7 @@ defmodule Pleroma.UserTest do      {:ok, user, _activity} = User.unfollow(user, followed) -    user = Repo.get(User, user.id) +    user = User.get_by_id(user.id)      assert user.following == []    end @@ -131,7 +197,7 @@ defmodule Pleroma.UserTest do      {:error, _} = User.unfollow(user, user) -    user = Repo.get(User, user.id) +    user = User.get_by_id(user.id)      assert user.following == [user.ap_id]    end @@ -143,6 +209,13 @@ defmodule Pleroma.UserTest do      refute User.following?(followed, user)    end +  test "fetches correct profile for nickname beginning with number" do +    # Use old-style integer ID to try to reproduce the problem +    user = insert(:user, %{id: 1080}) +    userwithnumbers = insert(:user, %{nickname: "#{user.id}garbage"}) +    assert userwithnumbers == User.get_cached_by_nickname_or_id(userwithnumbers.nickname) +  end +    describe "user registration" do      @full_user_data %{        bio: "A guy", @@ -168,6 +241,26 @@ defmodule Pleroma.UserTest do        assert User.following?(registered_user, user)        refute User.following?(registered_user, remote_user) + +      Pleroma.Config.put([:instance, :autofollowed_nicknames], []) +    end + +    test "it sends a welcome message if it is set" do +      welcome_user = insert(:user) + +      Pleroma.Config.put([:instance, :welcome_user_nickname], welcome_user.nickname) +      Pleroma.Config.put([:instance, :welcome_message], "Hello, this is a cool site") + +      cng = User.register_changeset(%User{}, @full_user_data) +      {:ok, registered_user} = User.register(cng) + +      activity = Repo.one(Pleroma.Activity) +      assert registered_user.ap_id in activity.recipients +      assert activity.data["object"]["content"] =~ "cool site" +      assert activity.actor == welcome_user.ap_id + +      Pleroma.Config.put([:instance, :welcome_user_nickname], nil) +      Pleroma.Config.put([:instance, :welcome_message], nil)      end      test "it requires an email, name, nickname and password, bio is optional" do @@ -546,6 +639,29 @@ defmodule Pleroma.UserTest do      end    end +  describe "mutes" do +    test "it mutes people" do +      user = insert(:user) +      muted_user = insert(:user) + +      refute User.mutes?(user, muted_user) + +      {:ok, user} = User.mute(user, muted_user) + +      assert User.mutes?(user, muted_user) +    end + +    test "it unmutes users" do +      user = insert(:user) +      muted_user = insert(:user) + +      {:ok, user} = User.mute(user, muted_user) +      {:ok, user} = User.unmute(user, muted_user) + +      refute User.mutes?(user, muted_user) +    end +  end +    describe "blocks" do      test "it blocks people" do        user = insert(:user) @@ -579,7 +695,7 @@ defmodule Pleroma.UserTest do        assert User.following?(blocked, blocker)        {:ok, blocker} = User.block(blocker, blocked) -      blocked = Repo.get(User, blocked.id) +      blocked = User.get_by_id(blocked.id)        assert User.blocks?(blocker, blocked) @@ -597,7 +713,7 @@ defmodule Pleroma.UserTest do        refute User.following?(blocked, blocker)        {:ok, blocker} = User.block(blocker, blocked) -      blocked = Repo.get(User, blocked.id) +      blocked = User.get_by_id(blocked.id)        assert User.blocks?(blocker, blocked) @@ -615,13 +731,29 @@ defmodule Pleroma.UserTest do        assert User.following?(blocked, blocker)        {:ok, blocker} = User.block(blocker, blocked) -      blocked = Repo.get(User, blocked.id) +      blocked = User.get_by_id(blocked.id)        assert User.blocks?(blocker, blocked)        refute User.following?(blocker, blocked)        refute User.following?(blocked, blocker)      end + +    test "blocks tear down blocked->blocker subscription relationships" do +      blocker = insert(:user) +      blocked = insert(:user) + +      {:ok, blocker} = User.subscribe(blocked, blocker) + +      assert User.subscribed_to?(blocked, blocker) +      refute User.subscribed_to?(blocker, blocked) + +      {:ok, blocker} = User.block(blocker, blocked) + +      assert User.blocks?(blocker, blocked) +      refute User.subscribed_to?(blocker, blocked) +      refute User.subscribed_to?(blocked, blocker) +    end    end    describe "domain blocking" do @@ -692,6 +824,16 @@ defmodule Pleroma.UserTest do      assert false == user.info.deactivated    end +  test ".delete_user_activities deletes all create activities" do +    user = insert(:user) + +    {:ok, activity} = CommonAPI.post(user, %{"status" => "2hu"}) +    {:ok, _} = User.delete_user_activities(user) + +    # TODO: Remove favorites, repeats, delete activities. +    refute Activity.get_by_id(activity.id) +  end +    test ".delete deactivates a user, all follow relationships and all create activities" do      user = insert(:user)      followed = insert(:user) @@ -709,9 +851,9 @@ defmodule Pleroma.UserTest do      {:ok, _} = User.delete(user) -    followed = Repo.get(User, followed.id) -    follower = Repo.get(User, follower.id) -    user = Repo.get(User, user.id) +    followed = User.get_by_id(followed.id) +    follower = User.get_by_id(follower.id) +    user = User.get_by_id(user.id)      assert user.info.deactivated @@ -720,7 +862,7 @@ defmodule Pleroma.UserTest do      # TODO: Remove favorites, repeats, delete activities. -    refute Repo.get(Activity, activity.id) +    refute Activity.get_by_id(activity.id)    end    test "get_public_key_for_ap_id fetches a user that's not in the db" do @@ -780,7 +922,11 @@ defmodule Pleroma.UserTest do        user = insert(:user, %{nickname: "john"})        Enum.each(["john", "jo", "j"], fn query -> -        assert user == User.search(query) |> List.first() |> Map.put(:search_rank, nil) +        assert user == +                 User.search(query) +                 |> List.first() +                 |> Map.put(:search_rank, nil) +                 |> Map.put(:search_type, nil)        end)      end @@ -788,7 +934,11 @@ defmodule Pleroma.UserTest do        user = insert(:user, %{name: "John Doe"})        Enum.each(["John Doe", "JOHN", "doe", "j d", "j", "d"], fn query -> -        assert user == User.search(query) |> List.first() |> Map.put(:search_rank, nil) +        assert user == +                 User.search(query) +                 |> List.first() +                 |> Map.put(:search_rank, nil) +                 |> Map.put(:search_type, nil)        end)      end @@ -830,7 +980,8 @@ defmodule Pleroma.UserTest do        {:ok, follower} = User.follow(follower, u1)        {:ok, u1} = User.follow(u1, friend) -      assert [friend.id, follower.id, u2.id] == Enum.map(User.search("doe", false, u1), & &1.id) +      assert [friend.id, follower.id, u2.id] -- +               Enum.map(User.search("doe", resolve: false, for_user: u1), & &1.id) == []      end      test "finds a user whose name is nil" do @@ -841,6 +992,7 @@ defmodule Pleroma.UserTest do                 User.search("lain@pleroma.soykaf.com")                 |> List.first()                 |> Map.put(:search_rank, nil) +               |> Map.put(:search_type, nil)      end      test "does not yield false-positive matches" do @@ -850,6 +1002,16 @@ defmodule Pleroma.UserTest do          assert [] == User.search(query)        end)      end + +    test "works with URIs" do +      results = User.search("http://mastodon.example.org/users/admin", resolve: true) +      result = results |> List.first() + +      user = User.get_by_ap_id("http://mastodon.example.org/users/admin") + +      assert length(results) == 1 +      assert user == result |> Map.put(:search_rank, nil) |> Map.put(:search_type, nil) +    end    end    test "auth_active?/1 works correctly" do @@ -944,5 +1106,65 @@ defmodule Pleroma.UserTest do        assert expected_text == User.parse_bio(bio, user)      end + +    test "Adds rel=me on linkbacked urls" do +      user = insert(:user, ap_id: "http://social.example.org/users/lain") + +      bio = "http://example.org/rel_me/null" +      expected_text = "<a href=\"#{bio}\">#{bio}</a>" +      assert expected_text == User.parse_bio(bio, user) + +      bio = "http://example.org/rel_me/link" +      expected_text = "<a href=\"#{bio}\">#{bio}</a>" +      assert expected_text == User.parse_bio(bio, user) + +      bio = "http://example.org/rel_me/anchor" +      expected_text = "<a href=\"#{bio}\">#{bio}</a>" +      assert expected_text == User.parse_bio(bio, user) +    end +  end + +  test "bookmarks" do +    user = insert(:user) + +    {:ok, activity1} = +      CommonAPI.post(user, %{ +        "status" => "heweoo!" +      }) + +    id1 = activity1.data["object"]["id"] + +    {:ok, activity2} = +      CommonAPI.post(user, %{ +        "status" => "heweoo!" +      }) + +    id2 = activity2.data["object"]["id"] + +    assert {:ok, user_state1} = User.bookmark(user, id1) +    assert user_state1.bookmarks == [id1] + +    assert {:ok, user_state2} = User.unbookmark(user, id1) +    assert user_state2.bookmarks == [] + +    assert {:ok, user_state3} = User.bookmark(user, id2) +    assert user_state3.bookmarks == [id2] +  end + +  test "follower count is updated when a follower is blocked" do +    user = insert(:user) +    follower = insert(:user) +    follower2 = insert(:user) +    follower3 = insert(:user) + +    {:ok, follower} = Pleroma.User.follow(follower, user) +    {:ok, _follower2} = Pleroma.User.follow(follower2, user) +    {:ok, _follower3} = Pleroma.User.follow(follower3, user) + +    {:ok, _} = Pleroma.User.block(user, follower) + +    user_show = Pleroma.Web.TwitterAPI.UserView.render("show.json", %{user: user}) + +    assert Map.get(user_show, "followers_count") == 2    end  end diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs index 52e67f046..8dd8e7e0a 100644 --- a/test/web/activity_pub/activity_pub_controller_test.exs +++ b/test/web/activity_pub/activity_pub_controller_test.exs @@ -5,9 +5,12 @@  defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do    use Pleroma.Web.ConnCase    import Pleroma.Factory -  alias Pleroma.Web.ActivityPub.{UserView, ObjectView} -  alias Pleroma.{Object, Repo, User}    alias Pleroma.Activity +  alias Pleroma.Instances +  alias Pleroma.Object +  alias Pleroma.User +  alias Pleroma.Web.ActivityPub.ObjectView +  alias Pleroma.Web.ActivityPub.UserView    setup_all do      Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) @@ -37,7 +40,24 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do    end    describe "/users/:nickname" do -    test "it returns a json representation of the user", %{conn: conn} do +    test "it returns a json representation of the user with accept application/json", %{ +      conn: conn +    } do +      user = insert(:user) + +      conn = +        conn +        |> put_req_header("accept", "application/json") +        |> get("/users/#{user.nickname}") + +      user = User.get_by_id(user.id) + +      assert json_response(conn, 200) == UserView.render("user.json", %{user: user}) +    end + +    test "it returns a json representation of the user with accept application/activity+json", %{ +      conn: conn +    } do        user = insert(:user)        conn = @@ -45,14 +65,47 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do          |> put_req_header("accept", "application/activity+json")          |> get("/users/#{user.nickname}") -      user = Repo.get(User, user.id) +      user = User.get_by_id(user.id) + +      assert json_response(conn, 200) == UserView.render("user.json", %{user: user}) +    end + +    test "it returns a json representation of the user with accept application/ld+json", %{ +      conn: conn +    } do +      user = insert(:user) + +      conn = +        conn +        |> put_req_header( +          "accept", +          "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"" +        ) +        |> get("/users/#{user.nickname}") + +      user = User.get_by_id(user.id)        assert json_response(conn, 200) == UserView.render("user.json", %{user: user})      end    end    describe "/object/:uuid" do -    test "it returns a json representation of the object", %{conn: conn} do +    test "it returns a json representation of the object with accept application/json", %{ +      conn: conn +    } do +      note = insert(:note) +      uuid = String.split(note.data["id"], "/") |> List.last() + +      conn = +        conn +        |> put_req_header("accept", "application/json") +        |> get("/objects/#{uuid}") + +      assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note}) +    end + +    test "it returns a json representation of the object with accept application/activity+json", +         %{conn: conn} do        note = insert(:note)        uuid = String.split(note.data["id"], "/") |> List.last() @@ -64,6 +117,23 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do        assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})      end +    test "it returns a json representation of the object with accept application/ld+json", %{ +      conn: conn +    } do +      note = insert(:note) +      uuid = String.split(note.data["id"], "/") |> List.last() + +      conn = +        conn +        |> put_req_header( +          "accept", +          "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"" +        ) +        |> get("/objects/#{uuid}") + +      assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note}) +    end +      test "it returns 404 for non-public messages", %{conn: conn} do        note = insert(:direct_note)        uuid = String.split(note.data["id"], "/") |> List.last() @@ -144,6 +214,23 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do        :timer.sleep(500)        assert Activity.get_by_ap_id(data["id"])      end + +    test "it clears `unreachable` federation status of the sender", %{conn: conn} do +      data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!() + +      sender_url = data["actor"] +      Instances.set_consistently_unreachable(sender_url) +      refute Instances.reachable?(sender_url) + +      conn = +        conn +        |> assign(:valid_signature, true) +        |> put_req_header("content-type", "application/activity+json") +        |> post("/inbox", data) + +      assert "ok" == json_response(conn, 200) +      assert Instances.reachable?(sender_url) +    end    end    describe "/users/:nickname/inbox" do @@ -191,9 +278,43 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do        assert response(conn, 200) =~ note_activity.data["object"]["content"]      end + +    test "it clears `unreachable` federation status of the sender", %{conn: conn} do +      user = insert(:user) + +      data = +        File.read!("test/fixtures/mastodon-post-activity.json") +        |> Poison.decode!() +        |> Map.put("bcc", [user.ap_id]) + +      sender_host = URI.parse(data["actor"]).host +      Instances.set_consistently_unreachable(sender_host) +      refute Instances.reachable?(sender_host) + +      conn = +        conn +        |> assign(:valid_signature, true) +        |> put_req_header("content-type", "application/activity+json") +        |> post("/users/#{user.nickname}/inbox", data) + +      assert "ok" == json_response(conn, 200) +      assert Instances.reachable?(sender_host) +    end    end    describe "/users/:nickname/outbox" do +    test "it will not bomb when there is no activity", %{conn: conn} do +      user = insert(:user) + +      conn = +        conn +        |> put_req_header("accept", "application/activity+json") +        |> get("/users/#{user.nickname}/outbox") + +      result = json_response(conn, 200) +      assert user.ap_id <> "/outbox" == result["id"] +    end +      test "it returns a note activity in a collection", %{conn: conn} do        note_activity = insert(:note_activity)        user = User.get_cached_by_ap_id(note_activity.data["actor"]) @@ -348,9 +469,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do        assert result["first"]["orderedItems"] == [user.ap_id]      end -    test "it returns returns empty if the user has 'hide_network' set", %{conn: conn} do +    test "it returns returns empty if the user has 'hide_followers' set", %{conn: conn} do        user = insert(:user) -      user_two = insert(:user, %{info: %{hide_network: true}}) +      user_two = insert(:user, %{info: %{hide_followers: true}})        User.follow(user, user_two)        result = @@ -359,7 +480,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do          |> json_response(200)        assert result["first"]["orderedItems"] == [] -      assert result["totalItems"] == 1 +      assert result["totalItems"] == 0      end      test "it works for more than 10 users", %{conn: conn} do @@ -403,8 +524,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do        assert result["first"]["orderedItems"] == [user_two.ap_id]      end -    test "it returns returns empty if the user has 'hide_network' set", %{conn: conn} do -      user = insert(:user, %{info: %{hide_network: true}}) +    test "it returns returns empty if the user has 'hide_follows' set", %{conn: conn} do +      user = insert(:user, %{info: %{hide_follows: true}})        user_two = insert(:user)        User.follow(user, user_two) @@ -414,14 +535,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do          |> json_response(200)        assert result["first"]["orderedItems"] == [] -      assert result["totalItems"] == 1 +      assert result["totalItems"] == 0      end      test "it works for more than 10 users", %{conn: conn} do        user = insert(:user)        Enum.each(1..15, fn _ -> -        user = Repo.get(User, user.id) +        user = User.get_by_id(user.id)          other_user = insert(:user)          User.follow(user, other_user)        end) diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index 7895cf21d..17fec05b1 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -1,17 +1,21 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.ActivityPub.ActivityPubTest do    use Pleroma.DataCase +  alias Pleroma.Activity +  alias Pleroma.Builders.ActivityBuilder +  alias Pleroma.Instances +  alias Pleroma.Object +  alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.ActivityPub.Utils    alias Pleroma.Web.CommonAPI -  alias Pleroma.{Activity, Object, User} -  alias Pleroma.Builders.ActivityBuilder    import Pleroma.Factory    import Tesla.Mock +  import Mock    setup do      mock(fn env -> apply(HttpRequestMock, :request, [env]) end) @@ -51,6 +55,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do          ActivityPub.fetch_activities([], %{:visibility => "public", "actor_id" => user.ap_id})        assert activities == [public_activity] + +      activities = +        ActivityPub.fetch_activities([], %{ +          :visibility => ~w[private public], +          "actor_id" => user.ap_id +        }) + +      assert activities == [public_activity, private_activity]      end    end @@ -128,7 +140,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do        activity = insert(:note_activity)        {:ok, new_activity} = ActivityPub.insert(activity.data) -      assert activity == new_activity +      assert activity.id == new_activity.id      end      test "inserts a given map into the activity database, giving it an id if it has none." do @@ -201,6 +213,58 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do        assert activity.actor == user.ap_id        assert activity.recipients == ["user1", "user2", user.ap_id]      end + +    test "increases user note count only for public activities" do +      user = insert(:user) + +      {:ok, _} = +        CommonAPI.post(User.get_by_id(user.id), %{"status" => "1", "visibility" => "public"}) + +      {:ok, _} = +        CommonAPI.post(User.get_by_id(user.id), %{"status" => "2", "visibility" => "unlisted"}) + +      {:ok, _} = +        CommonAPI.post(User.get_by_id(user.id), %{"status" => "2", "visibility" => "private"}) + +      {:ok, _} = +        CommonAPI.post(User.get_by_id(user.id), %{"status" => "3", "visibility" => "direct"}) + +      user = User.get_by_id(user.id) +      assert user.info.note_count == 2 +    end + +    test "increases replies count" do +      user = insert(:user) +      user2 = insert(:user) + +      {:ok, activity} = CommonAPI.post(user, %{"status" => "1", "visibility" => "public"}) +      ap_id = activity.data["id"] +      reply_data = %{"status" => "1", "in_reply_to_status_id" => activity.id} + +      # public +      {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "public")) +      assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) +      assert data["object"]["repliesCount"] == 1 +      assert object.data["repliesCount"] == 1 + +      # unlisted +      {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "unlisted")) +      assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) +      assert data["object"]["repliesCount"] == 2 +      assert object.data["repliesCount"] == 2 + +      # private +      {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "private")) +      assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) +      assert data["object"]["repliesCount"] == 2 +      assert object.data["repliesCount"] == 2 + +      # direct +      {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "direct")) +      assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) +      assert data["object"]["repliesCount"] == 2 +      assert object.data["repliesCount"] == 2 +    end    end    describe "fetch activities for recipients" do @@ -239,7 +303,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do      booster = insert(:user)      {:ok, user} = User.block(user, %{ap_id: activity_one.data["actor"]}) -    activities = ActivityPub.fetch_activities([], %{"blocking_user" => user}) +    activities = +      ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})      assert Enum.member?(activities, activity_two)      assert Enum.member?(activities, activity_three) @@ -247,7 +312,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do      {:ok, user} = User.unblock(user, %{ap_id: activity_one.data["actor"]}) -    activities = ActivityPub.fetch_activities([], %{"blocking_user" => user}) +    activities = +      ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})      assert Enum.member?(activities, activity_two)      assert Enum.member?(activities, activity_three) @@ -256,16 +322,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do      {:ok, user} = User.block(user, %{ap_id: activity_three.data["actor"]})      {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster)      %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id) -    activity_three = Repo.get(Activity, activity_three.id) +    activity_three = Activity.get_by_id(activity_three.id) -    activities = ActivityPub.fetch_activities([], %{"blocking_user" => user}) +    activities = +      ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})      assert Enum.member?(activities, activity_two)      refute Enum.member?(activities, activity_three)      refute Enum.member?(activities, boost_activity)      assert Enum.member?(activities, activity_one) -    activities = ActivityPub.fetch_activities([], %{"blocking_user" => nil}) +    activities = +      ActivityPub.fetch_activities([], %{"blocking_user" => nil, "skip_preload" => true})      assert Enum.member?(activities, activity_two)      assert Enum.member?(activities, activity_three) @@ -273,6 +341,77 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do      assert Enum.member?(activities, activity_one)    end +  test "doesn't return muted activities" do +    activity_one = insert(:note_activity) +    activity_two = insert(:note_activity) +    activity_three = insert(:note_activity) +    user = insert(:user) +    booster = insert(:user) +    {:ok, user} = User.mute(user, %User{ap_id: activity_one.data["actor"]}) + +    activities = +      ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true}) + +    assert Enum.member?(activities, activity_two) +    assert Enum.member?(activities, activity_three) +    refute Enum.member?(activities, activity_one) + +    # Calling with 'with_muted' will deliver muted activities, too. +    activities = +      ActivityPub.fetch_activities([], %{ +        "muting_user" => user, +        "with_muted" => true, +        "skip_preload" => true +      }) + +    assert Enum.member?(activities, activity_two) +    assert Enum.member?(activities, activity_three) +    assert Enum.member?(activities, activity_one) + +    {:ok, user} = User.unmute(user, %User{ap_id: activity_one.data["actor"]}) + +    activities = +      ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true}) + +    assert Enum.member?(activities, activity_two) +    assert Enum.member?(activities, activity_three) +    assert Enum.member?(activities, activity_one) + +    {:ok, user} = User.mute(user, %User{ap_id: activity_three.data["actor"]}) +    {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster) +    %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id) +    activity_three = Activity.get_by_id(activity_three.id) + +    activities = +      ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true}) + +    assert Enum.member?(activities, activity_two) +    refute Enum.member?(activities, activity_three) +    refute Enum.member?(activities, boost_activity) +    assert Enum.member?(activities, activity_one) + +    activities = ActivityPub.fetch_activities([], %{"muting_user" => nil, "skip_preload" => true}) + +    assert Enum.member?(activities, activity_two) +    assert Enum.member?(activities, activity_three) +    assert Enum.member?(activities, boost_activity) +    assert Enum.member?(activities, activity_one) +  end + +  test "does include announces on request" do +    activity_three = insert(:note_activity) +    user = insert(:user) +    booster = insert(:user) + +    {:ok, user} = User.follow(user, booster) + +    {:ok, announce, _object} = CommonAPI.repeat(activity_three.id, booster) + +    [announce_activity] = ActivityPub.fetch_activities([user.ap_id | user.following]) + +    assert announce_activity.id == announce.id +  end +    test "excludes reblogs on request" do      user = insert(:user)      {:ok, expected_activity} = ActivityBuilder.insert(%{"type" => "Create"}, %{:user => user}) @@ -344,6 +483,33 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do        assert length(activities) == 20        assert last == last_expected      end + +    test "doesn't return reblogs for users for whom reblogs have been muted" do +      activity = insert(:note_activity) +      user = insert(:user) +      booster = insert(:user) +      {:ok, user} = CommonAPI.hide_reblogs(user, booster) + +      {:ok, activity, _} = CommonAPI.repeat(activity.id, booster) + +      activities = ActivityPub.fetch_activities([], %{"muting_user" => user}) + +      refute Enum.any?(activities, fn %{id: id} -> id == activity.id end) +    end + +    test "returns reblogs for users for whom reblogs have not been muted" do +      activity = insert(:note_activity) +      user = insert(:user) +      booster = insert(:user) +      {:ok, user} = CommonAPI.hide_reblogs(user, booster) +      {:ok, user} = CommonAPI.show_reblogs(user, booster) + +      {:ok, activity, _} = CommonAPI.repeat(activity.id, booster) + +      activities = ActivityPub.fetch_activities([], %{"muting_user" => user}) + +      assert Enum.any?(activities, fn %{id: id} -> id == activity.id end) +    end    end    describe "like an object" do @@ -393,7 +559,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do        {:ok, _, _, object} = ActivityPub.unlike(user, object)        assert object.data["like_count"] == 0 -      assert Repo.get(Activity, like_activity.id) == nil +      assert Activity.get_by_id(like_activity.id) == nil      end    end @@ -444,7 +610,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do        assert unannounce_activity.data["actor"] == user.ap_id        assert unannounce_activity.data["context"] == announce_activity.data["context"] -      assert Repo.get(Activity, announce_activity.id) == nil +      assert Activity.get_by_id(announce_activity.id) == nil      end    end @@ -469,16 +635,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do      end    end -  describe "fetch the latest Follow" do -    test "fetches the latest Follow activity" do -      %Activity{data: %{"type" => "Follow"}} = activity = insert(:follow_activity) -      follower = Repo.get_by(User, ap_id: activity.data["actor"]) -      followed = Repo.get_by(User, ap_id: activity.data["object"]) - -      assert activity == Utils.fetch_latest_follow(follower, followed) -    end -  end -    describe "fetching an object" do      test "it fetches an object" do        {:ok, object} = @@ -583,10 +739,89 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do        assert delete.data["actor"] == note.data["actor"]        assert delete.data["object"] == note.data["object"]["id"] -      assert Repo.get(Activity, delete.id) != nil +      assert Activity.get_by_id(delete.id) != nil        assert Repo.get(Object, object.id).data["type"] == "Tombstone"      end + +    test "decrements user note count only for public activities" do +      user = insert(:user, info: %{note_count: 10}) + +      {:ok, a1} = +        CommonAPI.post(User.get_by_id(user.id), %{"status" => "yeah", "visibility" => "public"}) + +      {:ok, a2} = +        CommonAPI.post(User.get_by_id(user.id), %{"status" => "yeah", "visibility" => "unlisted"}) + +      {:ok, a3} = +        CommonAPI.post(User.get_by_id(user.id), %{"status" => "yeah", "visibility" => "private"}) + +      {:ok, a4} = +        CommonAPI.post(User.get_by_id(user.id), %{"status" => "yeah", "visibility" => "direct"}) + +      {:ok, _} = a1.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete() +      {:ok, _} = a2.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete() +      {:ok, _} = a3.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete() +      {:ok, _} = a4.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete() + +      user = User.get_by_id(user.id) +      assert user.info.note_count == 10 +    end + +    test "it creates a delete activity and checks that it is also sent to users mentioned by the deleted object" do +      user = insert(:user) +      note = insert(:note_activity) + +      {:ok, object} = +        Object.get_by_ap_id(note.data["object"]["id"]) +        |> Object.change(%{ +          data: %{ +            "actor" => note.data["object"]["actor"], +            "id" => note.data["object"]["id"], +            "to" => [user.ap_id], +            "type" => "Note" +          } +        }) +        |> Object.update_and_set_cache() + +      {:ok, delete} = ActivityPub.delete(object) + +      assert user.ap_id in delete.data["to"] +    end + +    test "decreases reply count" do +      user = insert(:user) +      user2 = insert(:user) + +      {:ok, activity} = CommonAPI.post(user, %{"status" => "1", "visibility" => "public"}) +      reply_data = %{"status" => "1", "in_reply_to_status_id" => activity.id} +      ap_id = activity.data["id"] + +      {:ok, public_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "public")) +      {:ok, unlisted_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "unlisted")) +      {:ok, private_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "private")) +      {:ok, direct_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "direct")) + +      _ = CommonAPI.delete(direct_reply.id, user2) +      assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) +      assert data["object"]["repliesCount"] == 2 +      assert object.data["repliesCount"] == 2 + +      _ = CommonAPI.delete(private_reply.id, user2) +      assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) +      assert data["object"]["repliesCount"] == 2 +      assert object.data["repliesCount"] == 2 + +      _ = CommonAPI.delete(public_reply.id, user2) +      assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) +      assert data["object"]["repliesCount"] == 1 +      assert object.data["repliesCount"] == 1 + +      _ = CommonAPI.delete(unlisted_reply.id, user2) +      assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) +      assert data["object"]["repliesCount"] == 0 +      assert object.data["repliesCount"] == 0 +    end    end    describe "timeline post-processing" do @@ -623,10 +858,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do            "in_reply_to_status_id" => private_activity_2.id          }) -      assert user1.following == [user3.ap_id <> "/followers", user1.ap_id] -        activities = ActivityPub.fetch_activities([user1.ap_id | user1.following]) +      private_activity_1 = Activity.get_by_ap_id_with_object(private_activity_1.data["id"])        assert [public_activity, private_activity_1, private_activity_3] == activities        assert length(activities) == 3 @@ -698,6 +932,146 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do      assert 3 = length(activities)    end +  test "it can create a Flag activity" do +    reporter = insert(:user) +    target_account = insert(:user) +    {:ok, activity} = CommonAPI.post(target_account, %{"status" => "foobar"}) +    context = Utils.generate_context_id() +    content = "foobar" + +    reporter_ap_id = reporter.ap_id +    target_ap_id = target_account.ap_id +    activity_ap_id = activity.data["id"] + +    assert {:ok, activity} = +             ActivityPub.flag(%{ +               actor: reporter, +               context: context, +               account: target_account, +               statuses: [activity], +               content: content +             }) + +    assert %Activity{ +             actor: ^reporter_ap_id, +             data: %{ +               "type" => "Flag", +               "content" => ^content, +               "context" => ^context, +               "object" => [^target_ap_id, ^activity_ap_id] +             } +           } = activity +  end + +  describe "publish_one/1" do +    test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is not specified", +                   Instances, +                   [:passthrough], +                   [] do +      actor = insert(:user) +      inbox = "http://200.site/users/nick1/inbox" + +      assert {:ok, _} = ActivityPub.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) + +      assert called(Instances.set_reachable(inbox)) +    end + +    test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is set", +                   Instances, +                   [:passthrough], +                   [] do +      actor = insert(:user) +      inbox = "http://200.site/users/nick1/inbox" + +      assert {:ok, _} = +               ActivityPub.publish_one(%{ +                 inbox: inbox, +                 json: "{}", +                 actor: actor, +                 id: 1, +                 unreachable_since: NaiveDateTime.utc_now() +               }) + +      assert called(Instances.set_reachable(inbox)) +    end + +    test_with_mock "does NOT call `Instances.set_reachable` on successful federation if `unreachable_since` is nil", +                   Instances, +                   [:passthrough], +                   [] do +      actor = insert(:user) +      inbox = "http://200.site/users/nick1/inbox" + +      assert {:ok, _} = +               ActivityPub.publish_one(%{ +                 inbox: inbox, +                 json: "{}", +                 actor: actor, +                 id: 1, +                 unreachable_since: nil +               }) + +      refute called(Instances.set_reachable(inbox)) +    end + +    test_with_mock "calls `Instances.set_unreachable` on target inbox on non-2xx HTTP response code", +                   Instances, +                   [:passthrough], +                   [] do +      actor = insert(:user) +      inbox = "http://404.site/users/nick1/inbox" + +      assert {:error, _} = +               ActivityPub.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) + +      assert called(Instances.set_unreachable(inbox)) +    end + +    test_with_mock "it calls `Instances.set_unreachable` on target inbox on request error of any kind", +                   Instances, +                   [:passthrough], +                   [] do +      actor = insert(:user) +      inbox = "http://connrefused.site/users/nick1/inbox" + +      assert {:error, _} = +               ActivityPub.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) + +      assert called(Instances.set_unreachable(inbox)) +    end + +    test_with_mock "does NOT call `Instances.set_unreachable` if target is reachable", +                   Instances, +                   [:passthrough], +                   [] do +      actor = insert(:user) +      inbox = "http://200.site/users/nick1/inbox" + +      assert {:ok, _} = ActivityPub.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) + +      refute called(Instances.set_unreachable(inbox)) +    end + +    test_with_mock "does NOT call `Instances.set_unreachable` if target instance has non-nil `unreachable_since`", +                   Instances, +                   [:passthrough], +                   [] do +      actor = insert(:user) +      inbox = "http://connrefused.site/users/nick1/inbox" + +      assert {:error, _} = +               ActivityPub.publish_one(%{ +                 inbox: inbox, +                 json: "{}", +                 actor: actor, +                 id: 1, +                 unreachable_since: NaiveDateTime.utc_now() +               }) + +      refute called(Instances.set_unreachable(inbox)) +    end +  end +    def data_uri do      File.read!("test/fixtures/avatar_data_uri")    end diff --git a/test/web/activity_pub/mrf/anti_followbot_policy_test.exs b/test/web/activity_pub/mrf/anti_followbot_policy_test.exs index 2ea4f9d3f..37a7bfcf7 100644 --- a/test/web/activity_pub/mrf/anti_followbot_policy_test.exs +++ b/test/web/activity_pub/mrf/anti_followbot_policy_test.exs @@ -54,4 +54,19 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicyTest do      {:ok, _} = AntiFollowbotPolicy.filter(message)    end + +  test "it gracefully handles nil display names" do +    actor = insert(:user, %{name: nil}) +    target = insert(:user) + +    message = %{ +      "@context" => "https://www.w3.org/ns/activitystreams", +      "type" => "Follow", +      "actor" => actor.ap_id, +      "object" => target.ap_id, +      "id" => "https://example.com/activities/1234" +    } + +    {:ok, _} = AntiFollowbotPolicy.filter(message) +  end  end diff --git a/test/web/activity_pub/mrf/hellthread_policy_test.exs b/test/web/activity_pub/mrf/hellthread_policy_test.exs new file mode 100644 index 000000000..eb6ee4d04 --- /dev/null +++ b/test/web/activity_pub/mrf/hellthread_policy_test.exs @@ -0,0 +1,73 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicyTest do +  use Pleroma.DataCase +  import Pleroma.Factory + +  import Pleroma.Web.ActivityPub.MRF.HellthreadPolicy + +  setup do +    user = insert(:user) + +    message = %{ +      "actor" => user.ap_id, +      "cc" => [user.follower_address], +      "type" => "Create", +      "to" => [ +        "https://www.w3.org/ns/activitystreams#Public", +        "https://instance.tld/users/user1", +        "https://instance.tld/users/user2", +        "https://instance.tld/users/user3" +      ] +    } + +    [user: user, message: message] +  end + +  describe "reject" do +    test "rejects the message if the recipient count is above reject_threshold", %{ +      message: message +    } do +      Pleroma.Config.put([:mrf_hellthread], %{delist_threshold: 0, reject_threshold: 2}) + +      {:reject, nil} = filter(message) +    end + +    test "does not reject the message if the recipient count is below reject_threshold", %{ +      message: message +    } do +      Pleroma.Config.put([:mrf_hellthread], %{delist_threshold: 0, reject_threshold: 3}) + +      assert {:ok, ^message} = filter(message) +    end +  end + +  describe "delist" do +    test "delists the message if the recipient count is above delist_threshold", %{ +      user: user, +      message: message +    } do +      Pleroma.Config.put([:mrf_hellthread], %{delist_threshold: 2, reject_threshold: 0}) + +      {:ok, message} = filter(message) +      assert user.follower_address in message["to"] +      assert "https://www.w3.org/ns/activitystreams#Public" in message["cc"] +    end + +    test "does not delist the message if the recipient count is below delist_threshold", %{ +      message: message +    } do +      Pleroma.Config.put([:mrf_hellthread], %{delist_threshold: 4, reject_threshold: 0}) + +      assert {:ok, ^message} = filter(message) +    end +  end + +  test "excludes follower collection and public URI from threshold count", %{message: message} do +    Pleroma.Config.put([:mrf_hellthread], %{delist_threshold: 0, reject_threshold: 3}) + +    assert {:ok, ^message} = filter(message) +  end +end diff --git a/test/web/activity_pub/mrf/keyword_policy_test.exs b/test/web/activity_pub/mrf/keyword_policy_test.exs new file mode 100644 index 000000000..602892a37 --- /dev/null +++ b/test/web/activity_pub/mrf/keyword_policy_test.exs @@ -0,0 +1,219 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicyTest do +  use Pleroma.DataCase + +  alias Pleroma.Web.ActivityPub.MRF.KeywordPolicy + +  setup do +    Pleroma.Config.put([:mrf_keyword], %{reject: [], federated_timeline_removal: [], replace: []}) +  end + +  describe "rejecting based on keywords" do +    test "rejects if string matches in content" do +      Pleroma.Config.put([:mrf_keyword, :reject], ["pun"]) + +      message = %{ +        "type" => "Create", +        "object" => %{ +          "content" => "just a daily reminder that compLAINer is a good pun", +          "summary" => "" +        } +      } + +      assert {:reject, nil} == KeywordPolicy.filter(message) +    end + +    test "rejects if string matches in summary" do +      Pleroma.Config.put([:mrf_keyword, :reject], ["pun"]) + +      message = %{ +        "type" => "Create", +        "object" => %{ +          "summary" => "just a daily reminder that compLAINer is a good pun", +          "content" => "" +        } +      } + +      assert {:reject, nil} == KeywordPolicy.filter(message) +    end + +    test "rejects if regex matches in content" do +      Pleroma.Config.put([:mrf_keyword, :reject], [~r/comp[lL][aA][iI][nN]er/]) + +      assert true == +               Enum.all?(["complainer", "compLainer", "compLAiNer", "compLAINer"], fn content -> +                 message = %{ +                   "type" => "Create", +                   "object" => %{ +                     "content" => "just a daily reminder that #{content} is a good pun", +                     "summary" => "" +                   } +                 } + +                 {:reject, nil} == KeywordPolicy.filter(message) +               end) +    end + +    test "rejects if regex matches in summary" do +      Pleroma.Config.put([:mrf_keyword, :reject], [~r/comp[lL][aA][iI][nN]er/]) + +      assert true == +               Enum.all?(["complainer", "compLainer", "compLAiNer", "compLAINer"], fn content -> +                 message = %{ +                   "type" => "Create", +                   "object" => %{ +                     "summary" => "just a daily reminder that #{content} is a good pun", +                     "content" => "" +                   } +                 } + +                 {:reject, nil} == KeywordPolicy.filter(message) +               end) +    end +  end + +  describe "delisting from ftl based on keywords" do +    test "delists if string matches in content" do +      Pleroma.Config.put([:mrf_keyword, :federated_timeline_removal], ["pun"]) + +      message = %{ +        "to" => ["https://www.w3.org/ns/activitystreams#Public"], +        "type" => "Create", +        "object" => %{ +          "content" => "just a daily reminder that compLAINer is a good pun", +          "summary" => "" +        } +      } + +      {:ok, result} = KeywordPolicy.filter(message) +      assert ["https://www.w3.org/ns/activitystreams#Public"] == result["cc"] +      refute ["https://www.w3.org/ns/activitystreams#Public"] == result["to"] +    end + +    test "delists if string matches in summary" do +      Pleroma.Config.put([:mrf_keyword, :federated_timeline_removal], ["pun"]) + +      message = %{ +        "to" => ["https://www.w3.org/ns/activitystreams#Public"], +        "type" => "Create", +        "object" => %{ +          "summary" => "just a daily reminder that compLAINer is a good pun", +          "content" => "" +        } +      } + +      {:ok, result} = KeywordPolicy.filter(message) +      assert ["https://www.w3.org/ns/activitystreams#Public"] == result["cc"] +      refute ["https://www.w3.org/ns/activitystreams#Public"] == result["to"] +    end + +    test "delists if regex matches in content" do +      Pleroma.Config.put([:mrf_keyword, :federated_timeline_removal], [~r/comp[lL][aA][iI][nN]er/]) + +      assert true == +               Enum.all?(["complainer", "compLainer", "compLAiNer", "compLAINer"], fn content -> +                 message = %{ +                   "type" => "Create", +                   "to" => ["https://www.w3.org/ns/activitystreams#Public"], +                   "object" => %{ +                     "content" => "just a daily reminder that #{content} is a good pun", +                     "summary" => "" +                   } +                 } + +                 {:ok, result} = KeywordPolicy.filter(message) + +                 ["https://www.w3.org/ns/activitystreams#Public"] == result["cc"] and +                   not (["https://www.w3.org/ns/activitystreams#Public"] == result["to"]) +               end) +    end + +    test "delists if regex matches in summary" do +      Pleroma.Config.put([:mrf_keyword, :federated_timeline_removal], [~r/comp[lL][aA][iI][nN]er/]) + +      assert true == +               Enum.all?(["complainer", "compLainer", "compLAiNer", "compLAINer"], fn content -> +                 message = %{ +                   "type" => "Create", +                   "to" => ["https://www.w3.org/ns/activitystreams#Public"], +                   "object" => %{ +                     "summary" => "just a daily reminder that #{content} is a good pun", +                     "content" => "" +                   } +                 } + +                 {:ok, result} = KeywordPolicy.filter(message) + +                 ["https://www.w3.org/ns/activitystreams#Public"] == result["cc"] and +                   not (["https://www.w3.org/ns/activitystreams#Public"] == result["to"]) +               end) +    end +  end + +  describe "replacing keywords" do +    test "replaces keyword if string matches in content" do +      Pleroma.Config.put([:mrf_keyword, :replace], [{"opensource", "free software"}]) + +      message = %{ +        "type" => "Create", +        "to" => ["https://www.w3.org/ns/activitystreams#Public"], +        "object" => %{"content" => "ZFS is opensource", "summary" => ""} +      } + +      {:ok, %{"object" => %{"content" => result}}} = KeywordPolicy.filter(message) +      assert result == "ZFS is free software" +    end + +    test "replaces keyword if string matches in summary" do +      Pleroma.Config.put([:mrf_keyword, :replace], [{"opensource", "free software"}]) + +      message = %{ +        "type" => "Create", +        "to" => ["https://www.w3.org/ns/activitystreams#Public"], +        "object" => %{"summary" => "ZFS is opensource", "content" => ""} +      } + +      {:ok, %{"object" => %{"summary" => result}}} = KeywordPolicy.filter(message) +      assert result == "ZFS is free software" +    end + +    test "replaces keyword if regex matches in content" do +      Pleroma.Config.put([:mrf_keyword, :replace], [ +        {~r/open(-|\s)?source\s?(software)?/, "free software"} +      ]) + +      assert true == +               Enum.all?(["opensource", "open-source", "open source"], fn content -> +                 message = %{ +                   "type" => "Create", +                   "to" => ["https://www.w3.org/ns/activitystreams#Public"], +                   "object" => %{"content" => "ZFS is #{content}", "summary" => ""} +                 } + +                 {:ok, %{"object" => %{"content" => result}}} = KeywordPolicy.filter(message) +                 result == "ZFS is free software" +               end) +    end + +    test "replaces keyword if regex matches in summary" do +      Pleroma.Config.put([:mrf_keyword, :replace], [ +        {~r/open(-|\s)?source\s?(software)?/, "free software"} +      ]) + +      assert true == +               Enum.all?(["opensource", "open-source", "open source"], fn content -> +                 message = %{ +                   "type" => "Create", +                   "to" => ["https://www.w3.org/ns/activitystreams#Public"], +                   "object" => %{"summary" => "ZFS is #{content}", "content" => ""} +                 } + +                 {:ok, %{"object" => %{"summary" => result}}} = KeywordPolicy.filter(message) +                 result == "ZFS is free software" +               end) +    end +  end +end diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index e5e3c8d33..47cffe257 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -4,13 +4,14 @@  defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do    use Pleroma.DataCase +  alias Pleroma.Activity +  alias Pleroma.Object +  alias Pleroma.Repo +  alias Pleroma.User +  alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.ActivityPub.Transmogrifier    alias Pleroma.Web.ActivityPub.Utils -  alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.OStatus -  alias Pleroma.Activity -  alias Pleroma.User -  alias Pleroma.Repo    alias Pleroma.Web.Websub.WebsubClientSubscription    import Pleroma.Factory @@ -334,6 +335,53 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        assert data["to"] == ["http://mastodon.example.org/users/admin/followers"]      end +    test "it ensures that as:Public activities make it to their followers collection" do +      user = insert(:user) + +      data = +        File.read!("test/fixtures/mastodon-post-activity.json") +        |> Poison.decode!() +        |> Map.put("actor", user.ap_id) +        |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"]) +        |> Map.put("cc", []) + +      object = +        data["object"] +        |> Map.put("attributedTo", user.ap_id) +        |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"]) +        |> Map.put("cc", []) + +      data = Map.put(data, "object", object) + +      {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + +      assert data["cc"] == [User.ap_followers(user)] +    end + +    test "it ensures that address fields become lists" do +      user = insert(:user) + +      data = +        File.read!("test/fixtures/mastodon-post-activity.json") +        |> Poison.decode!() +        |> Map.put("actor", user.ap_id) +        |> Map.put("to", nil) +        |> Map.put("cc", nil) + +      object = +        data["object"] +        |> Map.put("attributedTo", user.ap_id) +        |> Map.put("to", nil) +        |> Map.put("cc", nil) + +      data = Map.put(data, "object", object) + +      {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + +      assert !is_nil(data["to"]) +      assert !is_nil(data["cc"]) +    end +      test "it works for incoming update activities" do        data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!() @@ -413,7 +461,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        {:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(data) -      refute Repo.get(Activity, activity.id) +      refute Activity.get_by_id(activity.id)      end      test "it fails for incoming deletes with spoofed origin" do @@ -433,7 +481,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        :error = Transmogrifier.handle_incoming(data) -      assert Repo.get(Activity, activity.id) +      assert Activity.get_by_id(activity.id)      end      test "it works for incoming unannounces with an existing notice" do @@ -591,7 +639,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        assert activity.data["object"] == follow_activity.data["id"] -      follower = Repo.get(User, follower.id) +      follower = User.get_by_id(follower.id)        assert User.following?(follower, followed) == true      end @@ -613,7 +661,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        {:ok, activity} = Transmogrifier.handle_incoming(accept_data)        assert activity.data["object"] == follow_activity.data["id"] -      follower = Repo.get(User, follower.id) +      follower = User.get_by_id(follower.id)        assert User.following?(follower, followed) == true      end @@ -633,7 +681,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        {:ok, activity} = Transmogrifier.handle_incoming(accept_data)        assert activity.data["object"] == follow_activity.data["id"] -      follower = Repo.get(User, follower.id) +      follower = User.get_by_id(follower.id)        assert User.following?(follower, followed) == true      end @@ -652,7 +700,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        :error = Transmogrifier.handle_incoming(accept_data) -      follower = Repo.get(User, follower.id) +      follower = User.get_by_id(follower.id)        refute User.following?(follower, followed) == true      end @@ -671,7 +719,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        :error = Transmogrifier.handle_incoming(accept_data) -      follower = Repo.get(User, follower.id) +      follower = User.get_by_id(follower.id)        refute User.following?(follower, followed) == true      end @@ -696,7 +744,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        {:ok, activity} = Transmogrifier.handle_incoming(reject_data)        refute activity.local -      follower = Repo.get(User, follower.id) +      follower = User.get_by_id(follower.id)        assert User.following?(follower, followed) == false      end @@ -718,7 +766,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        {:ok, %Activity{data: _}} = Transmogrifier.handle_incoming(reject_data) -      follower = Repo.get(User, follower.id) +      follower = User.get_by_id(follower.id)        assert User.following?(follower, followed) == false      end @@ -764,6 +812,30 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        assert object.data["attachment"] == [attachment]      end + +    test "it accepts Flag activities" do +      user = insert(:user) +      other_user = insert(:user) + +      {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"}) +      object = Object.normalize(activity.data["object"]) + +      message = %{ +        "@context" => "https://www.w3.org/ns/activitystreams", +        "cc" => [user.ap_id], +        "object" => [user.ap_id, object.data["id"]], +        "type" => "Flag", +        "content" => "blocked AND reported!!!", +        "actor" => other_user.ap_id +      } + +      assert {:ok, activity} = Transmogrifier.handle_incoming(message) + +      assert activity.data["object"] == [user.ap_id, object.data["id"]] +      assert activity.data["content"] == "blocked AND reported!!!" +      assert activity.data["actor"] == other_user.ap_id +      assert activity.data["cc"] == [user.ap_id] +    end    end    describe "prepare outgoing" do @@ -948,7 +1020,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        {:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"})        assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients -      user = Repo.get(User, user.id) +      user = User.get_by_id(user.id)        assert user.info.note_count == 1        {:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye") @@ -956,13 +1028,10 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        assert user.info.note_count == 1        assert user.follower_address == "https://niu.moe/users/rye/followers" -      # Wait for the background task -      :timer.sleep(1000) - -      user = Repo.get(User, user.id) +      user = User.get_by_id(user.id)        assert user.info.note_count == 1 -      activity = Repo.get(Activity, activity.id) +      activity = Activity.get_by_id(activity.id)        assert user.follower_address in activity.recipients        assert %{ @@ -985,10 +1054,10 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        refute "..." in activity.recipients -      unrelated_activity = Repo.get(Activity, unrelated_activity.id) +      unrelated_activity = Activity.get_by_id(unrelated_activity.id)        refute user.follower_address in unrelated_activity.recipients -      user_two = Repo.get(User, user_two.id) +      user_two = User.get_by_id(user_two.id)        assert user.follower_address in user_two.following        refute "..." in user_two.following      end @@ -1128,4 +1197,58 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do          )      end    end + +  describe "reserialization" do +    test "successfully reserializes a message with inReplyTo == nil" do +      user = insert(:user) + +      message = %{ +        "@context" => "https://www.w3.org/ns/activitystreams", +        "to" => ["https://www.w3.org/ns/activitystreams#Public"], +        "cc" => [], +        "type" => "Create", +        "object" => %{ +          "to" => ["https://www.w3.org/ns/activitystreams#Public"], +          "cc" => [], +          "type" => "Note", +          "content" => "Hi", +          "inReplyTo" => nil, +          "attributedTo" => user.ap_id +        }, +        "actor" => user.ap_id +      } + +      {:ok, activity} = Transmogrifier.handle_incoming(message) + +      {:ok, _} = Transmogrifier.prepare_outgoing(activity.data) +    end + +    test "successfully reserializes a message with AS2 objects in IR" do +      user = insert(:user) + +      message = %{ +        "@context" => "https://www.w3.org/ns/activitystreams", +        "to" => ["https://www.w3.org/ns/activitystreams#Public"], +        "cc" => [], +        "type" => "Create", +        "object" => %{ +          "to" => ["https://www.w3.org/ns/activitystreams#Public"], +          "cc" => [], +          "type" => "Note", +          "content" => "Hi", +          "inReplyTo" => nil, +          "attributedTo" => user.ap_id, +          "tag" => [ +            %{"name" => "#2hu", "href" => "http://example.com/2hu", "type" => "Hashtag"}, +            %{"name" => "Bob", "href" => "http://example.com/bob", "type" => "Mention"} +          ] +        }, +        "actor" => user.ap_id +      } + +      {:ok, activity} = Transmogrifier.handle_incoming(message) + +      {:ok, _} = Transmogrifier.prepare_outgoing(activity.data) +    end +  end  end diff --git a/test/web/activity_pub/utils_test.exs b/test/web/activity_pub/utils_test.exs index aeed0564c..758214e68 100644 --- a/test/web/activity_pub/utils_test.exs +++ b/test/web/activity_pub/utils_test.exs @@ -1,6 +1,33 @@  defmodule Pleroma.Web.ActivityPub.UtilsTest do    use Pleroma.DataCase +  alias Pleroma.Activity +  alias Pleroma.Repo +  alias Pleroma.User +  alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.ActivityPub.Utils +  alias Pleroma.Web.CommonAPI + +  import Pleroma.Factory + +  describe "fetch the latest Follow" do +    test "fetches the latest Follow activity" do +      %Activity{data: %{"type" => "Follow"}} = activity = insert(:follow_activity) +      follower = Repo.get_by(User, ap_id: activity.data["actor"]) +      followed = Repo.get_by(User, ap_id: activity.data["object"]) + +      assert activity == Utils.fetch_latest_follow(follower, followed) +    end +  end + +  describe "fetch the latest Block" do +    test "fetches the latest Block activity" do +      blocker = insert(:user) +      blocked = insert(:user) +      {:ok, activity} = ActivityPub.block(blocker, blocked) + +      assert activity == Utils.fetch_latest_block(blocker, blocked) +    end +  end    describe "determine_explicit_mentions()" do      test "works with an object that has mentions" do @@ -54,4 +81,128 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do        assert Utils.determine_explicit_mentions(object) == []      end    end + +  describe "make_like_data" do +    setup do +      user = insert(:user) +      other_user = insert(:user) +      third_user = insert(:user) +      [user: user, other_user: other_user, third_user: third_user] +    end + +    test "addresses actor's follower address if the activity is public", %{ +      user: user, +      other_user: other_user, +      third_user: third_user +    } do +      expected_to = Enum.sort([user.ap_id, other_user.follower_address]) +      expected_cc = Enum.sort(["https://www.w3.org/ns/activitystreams#Public", third_user.ap_id]) + +      {:ok, activity} = +        CommonAPI.post(user, %{ +          "status" => +            "hey @#{other_user.nickname}, @#{third_user.nickname} how about beering together this weekend?" +        }) + +      %{"to" => to, "cc" => cc} = Utils.make_like_data(other_user, activity, nil) +      assert Enum.sort(to) == expected_to +      assert Enum.sort(cc) == expected_cc +    end + +    test "does not adress actor's follower address if the activity is not public", %{ +      user: user, +      other_user: other_user, +      third_user: third_user +    } do +      expected_to = Enum.sort([user.ap_id]) +      expected_cc = [third_user.ap_id] + +      {:ok, activity} = +        CommonAPI.post(user, %{ +          "status" => "@#{other_user.nickname} @#{third_user.nickname} bought a new swimsuit!", +          "visibility" => "private" +        }) + +      %{"to" => to, "cc" => cc} = Utils.make_like_data(other_user, activity, nil) +      assert Enum.sort(to) == expected_to +      assert Enum.sort(cc) == expected_cc +    end +  end + +  describe "fetch_ordered_collection" do +    import Tesla.Mock + +    test "fetches the first OrderedCollectionPage when an OrderedCollection is encountered" do +      mock(fn +        %{method: :get, url: "http://mastodon.com/outbox"} -> +          json(%{"type" => "OrderedCollection", "first" => "http://mastodon.com/outbox?page=true"}) + +        %{method: :get, url: "http://mastodon.com/outbox?page=true"} -> +          json(%{"type" => "OrderedCollectionPage", "orderedItems" => ["ok"]}) +      end) + +      assert Utils.fetch_ordered_collection("http://mastodon.com/outbox", 1) == ["ok"] +    end + +    test "fetches several pages in the right order one after another, but only the specified amount" do +      mock(fn +        %{method: :get, url: "http://example.com/outbox"} -> +          json(%{ +            "type" => "OrderedCollectionPage", +            "orderedItems" => [0], +            "next" => "http://example.com/outbox?page=1" +          }) + +        %{method: :get, url: "http://example.com/outbox?page=1"} -> +          json(%{ +            "type" => "OrderedCollectionPage", +            "orderedItems" => [1], +            "next" => "http://example.com/outbox?page=2" +          }) + +        %{method: :get, url: "http://example.com/outbox?page=2"} -> +          json(%{"type" => "OrderedCollectionPage", "orderedItems" => [2]}) +      end) + +      assert Utils.fetch_ordered_collection("http://example.com/outbox", 0) == [0] +      assert Utils.fetch_ordered_collection("http://example.com/outbox", 1) == [0, 1] +    end + +    test "returns an error if the url doesn't have an OrderedCollection/Page" do +      mock(fn +        %{method: :get, url: "http://example.com/not-an-outbox"} -> +          json(%{"type" => "NotAnOutbox"}) +      end) + +      assert {:error, _} = Utils.fetch_ordered_collection("http://example.com/not-an-outbox", 1) +    end + +    test "returns the what was collected if there are less pages than specified" do +      mock(fn +        %{method: :get, url: "http://example.com/outbox"} -> +          json(%{ +            "type" => "OrderedCollectionPage", +            "orderedItems" => [0], +            "next" => "http://example.com/outbox?page=1" +          }) + +        %{method: :get, url: "http://example.com/outbox?page=1"} -> +          json(%{"type" => "OrderedCollectionPage", "orderedItems" => [1]}) +      end) + +      assert Utils.fetch_ordered_collection("http://example.com/outbox", 5) == [0, 1] +    end +  end + +  test "make_json_ld_header/0" do +    assert Utils.make_json_ld_header() == %{ +             "@context" => [ +               "https://www.w3.org/ns/activitystreams", +               "http://localhost:4001/schemas/litepub-0.1.jsonld", +               %{ +                 "@language" => "und" +               } +             ] +           } +  end  end diff --git a/test/web/activity_pub/views/object_view_test.exs b/test/web/activity_pub/views/object_view_test.exs index d144a77fc..d939fc5a7 100644 --- a/test/web/activity_pub/views/object_view_test.exs +++ b/test/web/activity_pub/views/object_view_test.exs @@ -2,8 +2,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectViewTest do    use Pleroma.DataCase    import Pleroma.Factory -  alias Pleroma.Web.CommonAPI    alias Pleroma.Web.ActivityPub.ObjectView +  alias Pleroma.Web.CommonAPI    test "renders a note object" do      note = insert(:note) diff --git a/test/web/activity_pub/views/user_view_test.exs b/test/web/activity_pub/views/user_view_test.exs index 7fc870e96..9fb9455d2 100644 --- a/test/web/activity_pub/views/user_view_test.exs +++ b/test/web/activity_pub/views/user_view_test.exs @@ -15,4 +15,66 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do      assert String.contains?(result["publicKey"]["publicKeyPem"], "BEGIN PUBLIC KEY")    end + +  test "Does not add an avatar image if the user hasn't set one" do +    user = insert(:user) +    {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user) + +    result = UserView.render("user.json", %{user: user}) +    refute result["icon"] +    refute result["image"] + +    user = +      insert(:user, +        avatar: %{"url" => [%{"href" => "https://someurl"}]}, +        info: %{ +          banner: %{"url" => [%{"href" => "https://somebanner"}]} +        } +      ) + +    {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user) + +    result = UserView.render("user.json", %{user: user}) +    assert result["icon"]["url"] == "https://someurl" +    assert result["image"]["url"] == "https://somebanner" +  end + +  describe "endpoints" do +    test "local users have a usable endpoints structure" do +      user = insert(:user) +      {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user) + +      result = UserView.render("user.json", %{user: user}) + +      assert result["id"] == user.ap_id + +      %{ +        "sharedInbox" => _, +        "oauthAuthorizationEndpoint" => _, +        "oauthRegistrationEndpoint" => _, +        "oauthTokenEndpoint" => _ +      } = result["endpoints"] +    end + +    test "remote users have an empty endpoints structure" do +      user = insert(:user, local: false) +      {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user) + +      result = UserView.render("user.json", %{user: user}) + +      assert result["id"] == user.ap_id +      assert result["endpoints"] == %{} +    end + +    test "instance users do not expose oAuth endpoints" do +      user = insert(:user, nickname: nil, local: true) +      {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user) + +      result = UserView.render("user.json", %{user: user}) + +      refute result["endpoints"]["oauthAuthorizationEndpoint"] +      refute result["endpoints"]["oauthRegistrationEndpoint"] +      refute result["endpoints"]["oauthTokenEndpoint"] +    end +  end  end diff --git a/test/web/activity_pub/visibilty_test.exs b/test/web/activity_pub/visibilty_test.exs new file mode 100644 index 000000000..24b96c4aa --- /dev/null +++ b/test/web/activity_pub/visibilty_test.exs @@ -0,0 +1,98 @@ +defmodule Pleroma.Web.ActivityPub.VisibilityTest do +  use Pleroma.DataCase + +  alias Pleroma.Web.ActivityPub.Visibility +  alias Pleroma.Web.CommonAPI +  import Pleroma.Factory + +  setup do +    user = insert(:user) +    mentioned = insert(:user) +    following = insert(:user) +    unrelated = insert(:user) +    {:ok, following} = Pleroma.User.follow(following, user) + +    {:ok, public} = +      CommonAPI.post(user, %{"status" => "@#{mentioned.nickname}", "visibility" => "public"}) + +    {:ok, private} = +      CommonAPI.post(user, %{"status" => "@#{mentioned.nickname}", "visibility" => "private"}) + +    {:ok, direct} = +      CommonAPI.post(user, %{"status" => "@#{mentioned.nickname}", "visibility" => "direct"}) + +    {:ok, unlisted} = +      CommonAPI.post(user, %{"status" => "@#{mentioned.nickname}", "visibility" => "unlisted"}) + +    %{ +      public: public, +      private: private, +      direct: direct, +      unlisted: unlisted, +      user: user, +      mentioned: mentioned, +      following: following, +      unrelated: unrelated +    } +  end + +  test "is_direct?", %{public: public, private: private, direct: direct, unlisted: unlisted} do +    assert Visibility.is_direct?(direct) +    refute Visibility.is_direct?(public) +    refute Visibility.is_direct?(private) +    refute Visibility.is_direct?(unlisted) +  end + +  test "is_public?", %{public: public, private: private, direct: direct, unlisted: unlisted} do +    refute Visibility.is_public?(direct) +    assert Visibility.is_public?(public) +    refute Visibility.is_public?(private) +    assert Visibility.is_public?(unlisted) +  end + +  test "is_private?", %{public: public, private: private, direct: direct, unlisted: unlisted} do +    refute Visibility.is_private?(direct) +    refute Visibility.is_private?(public) +    assert Visibility.is_private?(private) +    refute Visibility.is_private?(unlisted) +  end + +  test "visible_for_user?", %{ +    public: public, +    private: private, +    direct: direct, +    unlisted: unlisted, +    user: user, +    mentioned: mentioned, +    following: following, +    unrelated: unrelated +  } do +    # All visible to author + +    assert Visibility.visible_for_user?(public, user) +    assert Visibility.visible_for_user?(private, user) +    assert Visibility.visible_for_user?(unlisted, user) +    assert Visibility.visible_for_user?(direct, user) + +    # All visible to a mentioned user + +    assert Visibility.visible_for_user?(public, mentioned) +    assert Visibility.visible_for_user?(private, mentioned) +    assert Visibility.visible_for_user?(unlisted, mentioned) +    assert Visibility.visible_for_user?(direct, mentioned) + +    # DM not visible for just follower + +    assert Visibility.visible_for_user?(public, following) +    assert Visibility.visible_for_user?(private, following) +    assert Visibility.visible_for_user?(unlisted, following) +    refute Visibility.visible_for_user?(direct, following) + +    # Public and unlisted visible for unrelated user + +    assert Visibility.visible_for_user?(public, unrelated) +    assert Visibility.visible_for_user?(unlisted, unrelated) +    refute Visibility.visible_for_user?(private, unrelated) +    refute Visibility.visible_for_user?(direct, unrelated) +  end +end diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index 42450a7b6..d44392c9d 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -5,7 +5,8 @@  defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do    use Pleroma.Web.ConnCase -  alias Pleroma.{Repo, User} +  alias Pleroma.User +  alias Pleroma.UserInviteToken    import Pleroma.Factory    describe "/api/pleroma/admin/user" do @@ -39,6 +40,85 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do      end    end +  describe "/api/pleroma/admin/users/:nickname" do +    test "Show", %{conn: conn} do +      admin = insert(:user, info: %{is_admin: true}) +      user = insert(:user) + +      conn = +        conn +        |> assign(:user, admin) +        |> get("/api/pleroma/admin/users/#{user.nickname}") + +      expected = %{ +        "deactivated" => false, +        "id" => to_string(user.id), +        "local" => true, +        "nickname" => user.nickname, +        "roles" => %{"admin" => false, "moderator" => false}, +        "tags" => [] +      } + +      assert expected == json_response(conn, 200) +    end + +    test "when the user doesn't exist", %{conn: conn} do +      admin = insert(:user, info: %{is_admin: true}) +      user = build(:user) + +      conn = +        conn +        |> assign(:user, admin) +        |> get("/api/pleroma/admin/users/#{user.nickname}") + +      assert "Not found" == json_response(conn, 404) +    end +  end + +  describe "/api/pleroma/admin/user/follow" do +    test "allows to force-follow another user" do +      admin = insert(:user, info: %{is_admin: true}) +      user = insert(:user) +      follower = insert(:user) + +      build_conn() +      |> assign(:user, admin) +      |> put_req_header("accept", "application/json") +      |> post("/api/pleroma/admin/user/follow", %{ +        "follower" => follower.nickname, +        "followed" => user.nickname +      }) + +      user = User.get_by_id(user.id) +      follower = User.get_by_id(follower.id) + +      assert User.following?(follower, user) +    end +  end + +  describe "/api/pleroma/admin/user/unfollow" do +    test "allows to force-unfollow another user" do +      admin = insert(:user, info: %{is_admin: true}) +      user = insert(:user) +      follower = insert(:user) + +      User.follow(follower, user) + +      build_conn() +      |> assign(:user, admin) +      |> put_req_header("accept", "application/json") +      |> post("/api/pleroma/admin/user/unfollow", %{ +        "follower" => follower.nickname, +        "followed" => user.nickname +      }) + +      user = User.get_by_id(user.id) +      follower = User.get_by_id(follower.id) + +      refute User.following?(follower, user) +    end +  end +    describe "PUT /api/pleroma/admin/users/tag" do      setup do        admin = insert(:user, info: %{is_admin: true}) @@ -65,13 +145,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do        user2: user2      } do        assert json_response(conn, :no_content) -      assert Repo.get(User, user1.id).tags == ["x", "foo", "bar"] -      assert Repo.get(User, user2.id).tags == ["y", "foo", "bar"] +      assert User.get_by_id(user1.id).tags == ["x", "foo", "bar"] +      assert User.get_by_id(user2.id).tags == ["y", "foo", "bar"]      end      test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do        assert json_response(conn, :no_content) -      assert Repo.get(User, user3.id).tags == ["unchanged"] +      assert User.get_by_id(user3.id).tags == ["unchanged"]      end    end @@ -101,13 +181,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do        user2: user2      } do        assert json_response(conn, :no_content) -      assert Repo.get(User, user1.id).tags == [] -      assert Repo.get(User, user2.id).tags == ["y"] +      assert User.get_by_id(user1.id).tags == [] +      assert User.get_by_id(user2.id).tags == ["y"]      end      test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do        assert json_response(conn, :no_content) -      assert Repo.get(User, user3.id).tags == ["unchanged"] +      assert User.get_by_id(user3.id).tags == ["unchanged"]      end    end @@ -158,6 +238,54 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do      end    end +  describe "PUT /api/pleroma/admin/activation_status" do +    setup %{conn: conn} do +      admin = insert(:user, info: %{is_admin: true}) + +      conn = +        conn +        |> assign(:user, admin) +        |> put_req_header("accept", "application/json") + +      %{conn: conn} +    end + +    test "deactivates the user", %{conn: conn} do +      user = insert(:user) + +      conn = +        conn +        |> put("/api/pleroma/admin/activation_status/#{user.nickname}", %{status: false}) + +      user = User.get_by_id(user.id) +      assert user.info.deactivated == true +      assert json_response(conn, :no_content) +    end + +    test "activates the user", %{conn: conn} do +      user = insert(:user, info: %{deactivated: true}) + +      conn = +        conn +        |> put("/api/pleroma/admin/activation_status/#{user.nickname}", %{status: true}) + +      user = User.get_by_id(user.id) +      assert user.info.deactivated == false +      assert json_response(conn, :no_content) +    end + +    test "returns 403 when requested by a non-admin", %{conn: conn} do +      user = insert(:user) + +      conn = +        conn +        |> assign(:user, user) +        |> put("/api/pleroma/admin/activation_status/#{user.nickname}", %{status: false}) + +      assert json_response(conn, :forbidden) +    end +  end +    describe "POST /api/pleroma/admin/email_invite, with valid config" do      setup do        registrations_open = Pleroma.Config.get([:instance, :registrations_open]) @@ -281,4 +409,368 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do      assert conn.status == 200    end + +  describe "GET /api/pleroma/admin/users" do +    test "renders users array for the first page" do +      admin = insert(:user, info: %{is_admin: true}) +      user = insert(:user, local: false, tags: ["foo", "bar"]) + +      conn = +        build_conn() +        |> assign(:user, admin) +        |> get("/api/pleroma/admin/users?page=1") + +      assert json_response(conn, 200) == %{ +               "count" => 2, +               "page_size" => 50, +               "users" => [ +                 %{ +                   "deactivated" => admin.info.deactivated, +                   "id" => admin.id, +                   "nickname" => admin.nickname, +                   "roles" => %{"admin" => true, "moderator" => false}, +                   "local" => true, +                   "tags" => [] +                 }, +                 %{ +                   "deactivated" => user.info.deactivated, +                   "id" => user.id, +                   "nickname" => user.nickname, +                   "roles" => %{"admin" => false, "moderator" => false}, +                   "local" => false, +                   "tags" => ["foo", "bar"] +                 } +               ] +             } +    end + +    test "renders empty array for the second page" do +      admin = insert(:user, info: %{is_admin: true}) +      insert(:user) + +      conn = +        build_conn() +        |> assign(:user, admin) +        |> get("/api/pleroma/admin/users?page=2") + +      assert json_response(conn, 200) == %{ +               "count" => 2, +               "page_size" => 50, +               "users" => [] +             } +    end + +    test "regular search" do +      admin = insert(:user, info: %{is_admin: true}) +      user = insert(:user, nickname: "bob") + +      conn = +        build_conn() +        |> assign(:user, admin) +        |> get("/api/pleroma/admin/users?query=bo") + +      assert json_response(conn, 200) == %{ +               "count" => 1, +               "page_size" => 50, +               "users" => [ +                 %{ +                   "deactivated" => user.info.deactivated, +                   "id" => user.id, +                   "nickname" => user.nickname, +                   "roles" => %{"admin" => false, "moderator" => false}, +                   "local" => true, +                   "tags" => [] +                 } +               ] +             } +    end + +    test "regular search with page size" do +      admin = insert(:user, info: %{is_admin: true}) +      user = insert(:user, nickname: "aalice") +      user2 = insert(:user, nickname: "alice") + +      conn = +        build_conn() +        |> assign(:user, admin) +        |> get("/api/pleroma/admin/users?query=a&page_size=1&page=1") + +      assert json_response(conn, 200) == %{ +               "count" => 2, +               "page_size" => 1, +               "users" => [ +                 %{ +                   "deactivated" => user.info.deactivated, +                   "id" => user.id, +                   "nickname" => user.nickname, +                   "roles" => %{"admin" => false, "moderator" => false}, +                   "local" => true, +                   "tags" => [] +                 } +               ] +             } + +      conn = +        build_conn() +        |> assign(:user, admin) +        |> get("/api/pleroma/admin/users?query=a&page_size=1&page=2") + +      assert json_response(conn, 200) == %{ +               "count" => 2, +               "page_size" => 1, +               "users" => [ +                 %{ +                   "deactivated" => user2.info.deactivated, +                   "id" => user2.id, +                   "nickname" => user2.nickname, +                   "roles" => %{"admin" => false, "moderator" => false}, +                   "local" => true, +                   "tags" => [] +                 } +               ] +             } +    end + +    test "only local users" do +      admin = insert(:user, info: %{is_admin: true}, nickname: "john") +      user = insert(:user, nickname: "bob") + +      insert(:user, nickname: "bobb", local: false) + +      conn = +        build_conn() +        |> assign(:user, admin) +        |> get("/api/pleroma/admin/users?query=bo&filters=local") + +      assert json_response(conn, 200) == %{ +               "count" => 1, +               "page_size" => 50, +               "users" => [ +                 %{ +                   "deactivated" => user.info.deactivated, +                   "id" => user.id, +                   "nickname" => user.nickname, +                   "roles" => %{"admin" => false, "moderator" => false}, +                   "local" => true, +                   "tags" => [] +                 } +               ] +             } +    end + +    test "only local users with no query" do +      admin = insert(:user, info: %{is_admin: true}, nickname: "john") +      user = insert(:user, nickname: "bob") + +      insert(:user, nickname: "bobb", local: false) + +      conn = +        build_conn() +        |> assign(:user, admin) +        |> get("/api/pleroma/admin/users?filters=local") + +      assert json_response(conn, 200) == %{ +               "count" => 2, +               "page_size" => 50, +               "users" => [ +                 %{ +                   "deactivated" => user.info.deactivated, +                   "id" => user.id, +                   "nickname" => user.nickname, +                   "roles" => %{"admin" => false, "moderator" => false}, +                   "local" => true, +                   "tags" => [] +                 }, +                 %{ +                   "deactivated" => admin.info.deactivated, +                   "id" => admin.id, +                   "nickname" => admin.nickname, +                   "roles" => %{"admin" => true, "moderator" => false}, +                   "local" => true, +                   "tags" => [] +                 } +               ] +             } +    end + +    test "it works with multiple filters" do +      admin = insert(:user, nickname: "john", info: %{is_admin: true}) +      user = insert(:user, nickname: "bob", local: false, info: %{deactivated: true}) + +      insert(:user, nickname: "ken", local: true, info: %{deactivated: true}) +      insert(:user, nickname: "bobb", local: false, info: %{deactivated: false}) + +      conn = +        build_conn() +        |> assign(:user, admin) +        |> get("/api/pleroma/admin/users?filters=deactivated,external") + +      assert json_response(conn, 200) == %{ +               "count" => 1, +               "page_size" => 50, +               "users" => [ +                 %{ +                   "deactivated" => user.info.deactivated, +                   "id" => user.id, +                   "nickname" => user.nickname, +                   "roles" => %{"admin" => false, "moderator" => false}, +                   "local" => user.local, +                   "tags" => [] +                 } +               ] +             } +    end +  end + +  test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation" do +    admin = insert(:user, info: %{is_admin: true}) +    user = insert(:user) + +    conn = +      build_conn() +      |> assign(:user, admin) +      |> patch("/api/pleroma/admin/users/#{user.nickname}/toggle_activation") + +    assert json_response(conn, 200) == +             %{ +               "deactivated" => !user.info.deactivated, +               "id" => user.id, +               "nickname" => user.nickname, +               "roles" => %{"admin" => false, "moderator" => false}, +               "local" => true, +               "tags" => [] +             } +  end + +  describe "GET /api/pleroma/admin/invite_token" do +    test "without options" do +      admin = insert(:user, info: %{is_admin: true}) + +      conn = +        build_conn() +        |> assign(:user, admin) +        |> get("/api/pleroma/admin/invite_token") + +      token = json_response(conn, 200) +      invite = UserInviteToken.find_by_token!(token) +      refute invite.used +      refute invite.expires_at +      refute invite.max_use +      assert invite.invite_type == "one_time" +    end + +    test "with expires_at" do +      admin = insert(:user, info: %{is_admin: true}) + +      conn = +        build_conn() +        |> assign(:user, admin) +        |> get("/api/pleroma/admin/invite_token", %{ +          "invite" => %{"expires_at" => Date.to_string(Date.utc_today())} +        }) + +      token = json_response(conn, 200) +      invite = UserInviteToken.find_by_token!(token) + +      refute invite.used +      assert invite.expires_at == Date.utc_today() +      refute invite.max_use +      assert invite.invite_type == "date_limited" +    end + +    test "with max_use" do +      admin = insert(:user, info: %{is_admin: true}) + +      conn = +        build_conn() +        |> assign(:user, admin) +        |> get("/api/pleroma/admin/invite_token", %{ +          "invite" => %{"max_use" => 150} +        }) + +      token = json_response(conn, 200) +      invite = UserInviteToken.find_by_token!(token) +      refute invite.used +      refute invite.expires_at +      assert invite.max_use == 150 +      assert invite.invite_type == "reusable" +    end + +    test "with max use and expires_at" do +      admin = insert(:user, info: %{is_admin: true}) + +      conn = +        build_conn() +        |> assign(:user, admin) +        |> get("/api/pleroma/admin/invite_token", %{ +          "invite" => %{"max_use" => 150, "expires_at" => Date.to_string(Date.utc_today())} +        }) + +      token = json_response(conn, 200) +      invite = UserInviteToken.find_by_token!(token) +      refute invite.used +      assert invite.expires_at == Date.utc_today() +      assert invite.max_use == 150 +      assert invite.invite_type == "reusable_date_limited" +    end +  end + +  describe "GET /api/pleroma/admin/invites" do +    test "no invites" do +      admin = insert(:user, info: %{is_admin: true}) + +      conn = +        build_conn() +        |> assign(:user, admin) +        |> get("/api/pleroma/admin/invites") + +      assert json_response(conn, 200) == %{"invites" => []} +    end + +    test "with invite" do +      admin = insert(:user, info: %{is_admin: true}) +      {:ok, invite} = UserInviteToken.create_invite() + +      conn = +        build_conn() +        |> assign(:user, admin) +        |> get("/api/pleroma/admin/invites") + +      assert json_response(conn, 200) == %{ +               "invites" => [ +                 %{ +                   "expires_at" => nil, +                   "id" => invite.id, +                   "invite_type" => "one_time", +                   "max_use" => nil, +                   "token" => invite.token, +                   "used" => false, +                   "uses" => 0 +                 } +               ] +             } +    end +  end + +  describe "POST /api/pleroma/admin/revoke_invite" do +    test "with token" do +      admin = insert(:user, info: %{is_admin: true}) +      {:ok, invite} = UserInviteToken.create_invite() + +      conn = +        build_conn() +        |> assign(:user, admin) +        |> post("/api/pleroma/admin/revoke_invite", %{"token" => invite.token}) + +      assert json_response(conn, 200) == %{ +               "expires_at" => nil, +               "id" => invite.id, +               "invite_type" => "one_time", +               "max_use" => nil, +               "token" => invite.token, +               "used" => true, +               "uses" => 0 +             } +    end +  end  end diff --git a/test/web/admin_api/search_test.exs b/test/web/admin_api/search_test.exs new file mode 100644 index 000000000..3950996ed --- /dev/null +++ b/test/web/admin_api/search_test.exs @@ -0,0 +1,88 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.SearchTest do +  use Pleroma.Web.ConnCase + +  alias Pleroma.Web.AdminAPI.Search + +  import Pleroma.Factory + +  describe "search for admin" do +    test "it ignores case" do +      insert(:user, nickname: "papercoach") +      insert(:user, nickname: "CanadaPaperCoach") + +      {:ok, _results, count} = +        Search.user(%{ +          query: "paper", +          local: false, +          page: 1, +          page_size: 50 +        }) + +      assert count == 2 +    end + +    test "it returns local/external users" do +      insert(:user, local: true) +      insert(:user, local: false) +      insert(:user, local: false) + +      {:ok, _results, local_count} = +        Search.user(%{ +          query: "", +          local: true +        }) + +      {:ok, _results, external_count} = +        Search.user(%{ +          query: "", +          external: true +        }) + +      assert local_count == 1 +      assert external_count == 2 +    end + +    test "it returns active/deactivated users" do +      insert(:user, info: %{deactivated: true}) +      insert(:user, info: %{deactivated: true}) +      insert(:user, info: %{deactivated: false}) + +      {:ok, _results, active_count} = +        Search.user(%{ +          query: "", +          active: true +        }) + +      {:ok, _results, deactivated_count} = +        Search.user(%{ +          query: "", +          deactivated: true +        }) + +      assert active_count == 1 +      assert deactivated_count == 2 +    end + +    test "it returns specific user" do +      insert(:user) +      insert(:user) +      insert(:user, nickname: "bob", local: true, info: %{deactivated: false}) + +      {:ok, _results, total_count} = Search.user(%{query: ""}) + +      {:ok, _results, count} = +        Search.user(%{ +          query: "Bo", +          active: true, +          local: true +        }) + +      assert total_count == 3 +      assert count == 1 +    end +  end +end diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index a7d9e6161..34aa5bf18 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -2,14 +2,32 @@  # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.CommonAPI.Test do +defmodule Pleroma.Web.CommonAPITest do    use Pleroma.DataCase -  alias Pleroma.Web.CommonAPI -  alias Pleroma.User    alias Pleroma.Activity +  alias Pleroma.User +  alias Pleroma.Web.CommonAPI    import Pleroma.Factory +  test "with the safe_dm_mention option set, it does not mention people beyond the initial tags" do +    har = insert(:user) +    jafnhar = insert(:user) +    tridi = insert(:user) +    option = Pleroma.Config.get([:instance, :safe_dm_mentions]) +    Pleroma.Config.put([:instance, :safe_dm_mentions], true) + +    {:ok, activity} = +      CommonAPI.post(har, %{ +        "status" => "@#{jafnhar.nickname} hey, i never want to see @#{tridi.nickname} again", +        "visibility" => "direct" +      }) + +    refute tridi.ap_id in activity.recipients +    assert jafnhar.ap_id in activity.recipients +    Pleroma.Config.put([:instance, :safe_dm_mentions], option) +  end +    test "it de-duplicates tags" do      user = insert(:user)      {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu #2HU"}) @@ -164,4 +182,84 @@ defmodule Pleroma.Web.CommonAPI.Test do        assert %User{info: %{pinned_activities: []}} = user      end    end + +  describe "mute tests" do +    setup do +      user = insert(:user) + +      activity = insert(:note_activity) + +      [user: user, activity: activity] +    end + +    test "add mute", %{user: user, activity: activity} do +      {:ok, _} = CommonAPI.add_mute(user, activity) +      assert CommonAPI.thread_muted?(user, activity) +    end + +    test "remove mute", %{user: user, activity: activity} do +      CommonAPI.add_mute(user, activity) +      {:ok, _} = CommonAPI.remove_mute(user, activity) +      refute CommonAPI.thread_muted?(user, activity) +    end + +    test "check that mutes can't be duplicate", %{user: user, activity: activity} do +      CommonAPI.add_mute(user, activity) +      {:error, _} = CommonAPI.add_mute(user, activity) +    end +  end + +  describe "reports" do +    test "creates a report" do +      reporter = insert(:user) +      target_user = insert(:user) + +      {:ok, activity} = CommonAPI.post(target_user, %{"status" => "foobar"}) + +      reporter_ap_id = reporter.ap_id +      target_ap_id = target_user.ap_id +      activity_ap_id = activity.data["id"] +      comment = "foobar" + +      report_data = %{ +        "account_id" => target_user.id, +        "comment" => comment, +        "status_ids" => [activity.id] +      } + +      assert {:ok, flag_activity} = CommonAPI.report(reporter, report_data) + +      assert %Activity{ +               actor: ^reporter_ap_id, +               data: %{ +                 "type" => "Flag", +                 "content" => ^comment, +                 "object" => [^target_ap_id, ^activity_ap_id] +               } +             } = flag_activity +    end +  end + +  describe "reblog muting" do +    setup do +      muter = insert(:user) + +      muted = insert(:user) + +      [muter: muter, muted: muted] +    end + +    test "add a reblog mute", %{muter: muter, muted: muted} do +      {:ok, muter} = CommonAPI.hide_reblogs(muter, muted) + +      assert Pleroma.User.showing_reblogs?(muter, muted) == false +    end + +    test "remove a reblog mute", %{muter: muter, muted: muted} do +      {:ok, muter} = CommonAPI.hide_reblogs(muter, muted) +      {:ok, muter} = CommonAPI.show_reblogs(muter, muted) + +      assert Pleroma.User.showing_reblogs?(muter, muted) == true +    end +  end  end diff --git a/test/web/common_api/common_api_utils_test.exs b/test/web/common_api/common_api_utils_test.exs index 754bc7255..f0c59d5c3 100644 --- a/test/web/common_api/common_api_utils_test.exs +++ b/test/web/common_api/common_api_utils_test.exs @@ -3,9 +3,10 @@  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.CommonAPI.UtilsTest do +  alias Pleroma.Builders.UserBuilder +  alias Pleroma.Object    alias Pleroma.Web.CommonAPI.Utils    alias Pleroma.Web.Endpoint -  alias Pleroma.Builders.{UserBuilder}    use Pleroma.DataCase    test "it adds attachment links to a given text and attachment set" do @@ -57,19 +58,19 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do      assert expected == Utils.emoji_from_profile(user)    end -  describe "format_input/4" do +  describe "format_input/3" do      test "works for bare text/plain" do        text = "hello world!"        expected = "hello world!" -      output = Utils.format_input(text, [], [], "text/plain") +      {output, [], []} = Utils.format_input(text, "text/plain")        assert output == expected        text = "hello world!\n\nsecond paragraph!"        expected = "hello world!<br><br>second paragraph!" -      output = Utils.format_input(text, [], [], "text/plain") +      {output, [], []} = Utils.format_input(text, "text/plain")        assert output == expected      end @@ -78,14 +79,14 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do        text = "<p>hello world!</p>"        expected = "<p>hello world!</p>" -      output = Utils.format_input(text, [], [], "text/html") +      {output, [], []} = Utils.format_input(text, "text/html")        assert output == expected        text = "<p>hello world!</p>\n\n<p>second paragraph</p>"        expected = "<p>hello world!</p>\n\n<p>second paragraph</p>" -      output = Utils.format_input(text, [], [], "text/html") +      {output, [], []} = Utils.format_input(text, "text/html")        assert output == expected      end @@ -94,16 +95,98 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do        text = "**hello world**"        expected = "<p><strong>hello world</strong></p>\n" -      output = Utils.format_input(text, [], [], "text/markdown") +      {output, [], []} = Utils.format_input(text, "text/markdown")        assert output == expected        text = "**hello world**\n\n*another paragraph*"        expected = "<p><strong>hello world</strong></p>\n<p><em>another paragraph</em></p>\n" -      output = Utils.format_input(text, [], [], "text/markdown") +      {output, [], []} = Utils.format_input(text, "text/markdown")        assert output == expected + +      text = """ +      > cool quote + +      by someone +      """ + +      expected = "<blockquote><p>cool quote</p>\n</blockquote>\n<p>by someone</p>\n" + +      {output, [], []} = Utils.format_input(text, "text/markdown") + +      assert output == expected +    end + +    test "works for text/markdown with mentions" do +      {:ok, user} = +        UserBuilder.insert(%{nickname: "user__test", ap_id: "http://foo.com/user__test"}) + +      text = "**hello world**\n\n*another @user__test and @user__test google.com paragraph*" + +      expected = +        "<p><strong>hello world</strong></p>\n<p><em>another <span class=\"h-card\"><a data-user=\"#{ +          user.id +        }\" class=\"u-url mention\" href=\"http://foo.com/user__test\">@<span>user__test</span></a></span> and <span class=\"h-card\"><a data-user=\"#{ +          user.id +        }\" class=\"u-url mention\" href=\"http://foo.com/user__test\">@<span>user__test</span></a></span> <a href=\"http://google.com\">google.com</a> paragraph</em></p>\n" + +      {output, _, _} = Utils.format_input(text, "text/markdown") + +      assert output == expected +    end +  end + +  describe "context_to_conversation_id" do +    test "creates a mapping object" do +      conversation_id = Utils.context_to_conversation_id("random context") +      object = Object.get_by_ap_id("random context") + +      assert conversation_id == object.id +    end + +    test "returns an existing mapping for an existing object" do +      {:ok, object} = Object.context_mapping("random context") |> Repo.insert() +      conversation_id = Utils.context_to_conversation_id("random context") + +      assert conversation_id == object.id +    end +  end + +  describe "formats date to asctime" do +    test "when date is in ISO 8601 format" do +      date = DateTime.utc_now() |> DateTime.to_iso8601() + +      expected = +        date +        |> DateTime.from_iso8601() +        |> elem(1) +        |> Calendar.Strftime.strftime!("%a %b %d %H:%M:%S %z %Y") + +      assert Utils.date_to_asctime(date) == expected +    end + +    test "when date is a binary in wrong format" do +      date = DateTime.utc_now() + +      expected = "" + +      assert Utils.date_to_asctime(date) == expected +    end + +    test "when date is a Unix timestamp" do +      date = DateTime.utc_now() |> DateTime.to_unix() + +      expected = "" + +      assert Utils.date_to_asctime(date) == expected +    end + +    test "when date is nil" do +      expected = "" + +      assert Utils.date_to_asctime(nil) == expected      end    end  end diff --git a/test/web/federator_test.exs b/test/web/federator_test.exs index a49265c0c..52729eb50 100644 --- a/test/web/federator_test.exs +++ b/test/web/federator_test.exs @@ -3,8 +3,9 @@  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.FederatorTest do -  alias Pleroma.Web.Federator +  alias Pleroma.Instances    alias Pleroma.Web.CommonAPI +  alias Pleroma.Web.Federator    use Pleroma.DataCase    import Pleroma.Factory    import Mock @@ -14,22 +15,6 @@ defmodule Pleroma.Web.FederatorTest do      :ok    end -  test "enqueues an element according to priority" do -    queue = [%{item: 1, priority: 2}] - -    new_queue = Federator.enqueue_sorted(queue, 2, 1) -    assert new_queue == [%{item: 2, priority: 1}, %{item: 1, priority: 2}] - -    new_queue = Federator.enqueue_sorted(queue, 2, 3) -    assert new_queue == [%{item: 1, priority: 2}, %{item: 2, priority: 3}] -  end - -  test "pop first item" do -    queue = [%{item: 2, priority: 1}, %{item: 1, priority: 2}] - -    assert {2, [%{item: 1, priority: 2}]} = Federator.queue_pop(queue) -  end -    describe "Publish an activity" do      setup do        user = insert(:user) @@ -49,7 +34,7 @@ defmodule Pleroma.Web.FederatorTest do        relay_mock: relay_mock      } do        with_mocks([relay_mock]) do -        Federator.handle(:publish, activity) +        Federator.publish(activity)        end        assert_received :relay_publish @@ -62,7 +47,7 @@ defmodule Pleroma.Web.FederatorTest do        Pleroma.Config.put([:instance, :allow_relay], false)        with_mocks([relay_mock]) do -        Federator.handle(:publish, activity) +        Federator.publish(activity)        end        refute_received :relay_publish @@ -71,6 +56,122 @@ defmodule Pleroma.Web.FederatorTest do      end    end +  describe "Targets reachability filtering in `publish`" do +    test_with_mock "it federates only to reachable instances via AP", +                   Federator, +                   [:passthrough], +                   [] do +      user = insert(:user) + +      {inbox1, inbox2} = +        {"https://domain.com/users/nick1/inbox", "https://domain2.com/users/nick2/inbox"} + +      insert(:user, %{ +        local: false, +        nickname: "nick1@domain.com", +        ap_id: "https://domain.com/users/nick1", +        info: %{ap_enabled: true, source_data: %{"inbox" => inbox1}} +      }) + +      insert(:user, %{ +        local: false, +        nickname: "nick2@domain2.com", +        ap_id: "https://domain2.com/users/nick2", +        info: %{ap_enabled: true, source_data: %{"inbox" => inbox2}} +      }) + +      dt = NaiveDateTime.utc_now() +      Instances.set_unreachable(inbox1, dt) + +      Instances.set_consistently_unreachable(URI.parse(inbox2).host) + +      {:ok, _activity} = +        CommonAPI.post(user, %{"status" => "HI @nick1@domain.com, @nick2@domain2.com!"}) + +      assert called(Federator.publish_single_ap(%{inbox: inbox1, unreachable_since: dt})) + +      refute called(Federator.publish_single_ap(%{inbox: inbox2})) +    end + +    test_with_mock "it federates only to reachable instances via Websub", +                   Federator, +                   [:passthrough], +                   [] do +      user = insert(:user) +      websub_topic = Pleroma.Web.OStatus.feed_path(user) + +      sub1 = +        insert(:websub_subscription, %{ +          topic: websub_topic, +          state: "active", +          callback: "http://pleroma.soykaf.com/cb" +        }) + +      sub2 = +        insert(:websub_subscription, %{ +          topic: websub_topic, +          state: "active", +          callback: "https://pleroma2.soykaf.com/cb" +        }) + +      dt = NaiveDateTime.utc_now() +      Instances.set_unreachable(sub2.callback, dt) + +      Instances.set_consistently_unreachable(sub1.callback) + +      {:ok, _activity} = CommonAPI.post(user, %{"status" => "HI"}) + +      assert called( +               Federator.publish_single_websub(%{ +                 callback: sub2.callback, +                 unreachable_since: dt +               }) +             ) + +      refute called(Federator.publish_single_websub(%{callback: sub1.callback})) +    end + +    test_with_mock "it federates only to reachable instances via Salmon", +                   Federator, +                   [:passthrough], +                   [] do +      user = insert(:user) + +      remote_user1 = +        insert(:user, %{ +          local: false, +          nickname: "nick1@domain.com", +          ap_id: "https://domain.com/users/nick1", +          info: %{salmon: "https://domain.com/salmon"} +        }) + +      remote_user2 = +        insert(:user, %{ +          local: false, +          nickname: "nick2@domain2.com", +          ap_id: "https://domain2.com/users/nick2", +          info: %{salmon: "https://domain2.com/salmon"} +        }) + +      dt = NaiveDateTime.utc_now() +      Instances.set_unreachable(remote_user2.ap_id, dt) + +      Instances.set_consistently_unreachable("domain.com") + +      {:ok, _activity} = +        CommonAPI.post(user, %{"status" => "HI @nick1@domain.com, @nick2@domain2.com!"}) + +      assert called( +               Federator.publish_single_salmon(%{ +                 recipient: remote_user2, +                 unreachable_since: dt +               }) +             ) + +      refute called(Federator.publish_single_websub(%{recipient: remote_user1})) +    end +  end +    describe "Receive an activity" do      test "successfully processes incoming AP docs with correct origin" do        params = %{ @@ -87,7 +188,7 @@ defmodule Pleroma.Web.FederatorTest do          "to" => ["https://www.w3.org/ns/activitystreams#Public"]        } -      {:ok, _activity} = Federator.handle(:incoming_ap_doc, params) +      {:ok, _activity} = Federator.incoming_ap_doc(params)      end      test "rejects incoming AP docs with incorrect origin" do @@ -105,7 +206,7 @@ defmodule Pleroma.Web.FederatorTest do          "to" => ["https://www.w3.org/ns/activitystreams#Public"]        } -      :error = Federator.handle(:incoming_ap_doc, params) +      :error = Federator.incoming_ap_doc(params)      end    end  end diff --git a/test/web/instances/instance_test.exs b/test/web/instances/instance_test.exs new file mode 100644 index 000000000..d28730994 --- /dev/null +++ b/test/web/instances/instance_test.exs @@ -0,0 +1,107 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Instances.InstanceTest do +  alias Pleroma.Instances.Instance +  alias Pleroma.Repo + +  use Pleroma.DataCase + +  import Pleroma.Factory + +  setup_all do +    config_path = [:instance, :federation_reachability_timeout_days] +    initial_setting = Pleroma.Config.get(config_path) + +    Pleroma.Config.put(config_path, 1) +    on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end) + +    :ok +  end + +  describe "set_reachable/1" do +    test "clears `unreachable_since` of existing matching Instance record having non-nil `unreachable_since`" do +      instance = insert(:instance, unreachable_since: NaiveDateTime.utc_now()) + +      assert {:ok, instance} = Instance.set_reachable(instance.host) +      refute instance.unreachable_since +    end + +    test "keeps nil `unreachable_since` of existing matching Instance record having nil `unreachable_since`" do +      instance = insert(:instance, unreachable_since: nil) + +      assert {:ok, instance} = Instance.set_reachable(instance.host) +      refute instance.unreachable_since +    end + +    test "does NOT create an Instance record in case of no existing matching record" do +      host = "domain.org" +      assert nil == Instance.set_reachable(host) + +      assert [] = Repo.all(Ecto.Query.from(i in Instance)) +      assert Instance.reachable?(host) +    end +  end + +  describe "set_unreachable/1" do +    test "creates new record having `unreachable_since` to current time if record does not exist" do +      assert {:ok, instance} = Instance.set_unreachable("https://domain.com/path") + +      instance = Repo.get(Instance, instance.id) +      assert instance.unreachable_since +      assert "domain.com" == instance.host +    end + +    test "sets `unreachable_since` of existing record having nil `unreachable_since`" do +      instance = insert(:instance, unreachable_since: nil) +      refute instance.unreachable_since + +      assert {:ok, _} = Instance.set_unreachable(instance.host) + +      instance = Repo.get(Instance, instance.id) +      assert instance.unreachable_since +    end + +    test "does NOT modify `unreachable_since` value of existing record in case it's present" do +      instance = +        insert(:instance, unreachable_since: NaiveDateTime.add(NaiveDateTime.utc_now(), -10)) + +      assert instance.unreachable_since +      initial_value = instance.unreachable_since + +      assert {:ok, _} = Instance.set_unreachable(instance.host) + +      instance = Repo.get(Instance, instance.id) +      assert initial_value == instance.unreachable_since +    end +  end + +  describe "set_unreachable/2" do +    test "sets `unreachable_since` value of existing record in case it's newer than supplied value" do +      instance = +        insert(:instance, unreachable_since: NaiveDateTime.add(NaiveDateTime.utc_now(), -10)) + +      assert instance.unreachable_since + +      past_value = NaiveDateTime.add(NaiveDateTime.utc_now(), -100) +      assert {:ok, _} = Instance.set_unreachable(instance.host, past_value) + +      instance = Repo.get(Instance, instance.id) +      assert past_value == instance.unreachable_since +    end + +    test "does NOT modify `unreachable_since` value of existing record in case it's equal to or older than supplied value" do +      instance = +        insert(:instance, unreachable_since: NaiveDateTime.add(NaiveDateTime.utc_now(), -10)) + +      assert instance.unreachable_since +      initial_value = instance.unreachable_since + +      assert {:ok, _} = Instance.set_unreachable(instance.host, NaiveDateTime.utc_now()) + +      instance = Repo.get(Instance, instance.id) +      assert initial_value == instance.unreachable_since +    end +  end +end diff --git a/test/web/instances/instances_test.exs b/test/web/instances/instances_test.exs new file mode 100644 index 000000000..f0d84edea --- /dev/null +++ b/test/web/instances/instances_test.exs @@ -0,0 +1,132 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.InstancesTest do +  alias Pleroma.Instances + +  use Pleroma.DataCase + +  setup_all do +    config_path = [:instance, :federation_reachability_timeout_days] +    initial_setting = Pleroma.Config.get(config_path) + +    Pleroma.Config.put(config_path, 1) +    on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end) + +    :ok +  end + +  describe "reachable?/1" do +    test "returns `true` for host / url with unknown reachability status" do +      assert Instances.reachable?("unknown.site") +      assert Instances.reachable?("http://unknown.site") +    end + +    test "returns `false` for host / url marked unreachable for at least `reachability_datetime_threshold()`" do +      host = "consistently-unreachable.name" +      Instances.set_consistently_unreachable(host) + +      refute Instances.reachable?(host) +      refute Instances.reachable?("http://#{host}/path") +    end + +    test "returns `true` for host / url marked unreachable for less than `reachability_datetime_threshold()`" do +      url = "http://eventually-unreachable.name/path" + +      Instances.set_unreachable(url) + +      assert Instances.reachable?(url) +      assert Instances.reachable?(URI.parse(url).host) +    end + +    test "returns true on non-binary input" do +      assert Instances.reachable?(nil) +      assert Instances.reachable?(1) +    end +  end + +  describe "filter_reachable/1" do +    setup do +      host = "consistently-unreachable.name" +      url1 = "http://eventually-unreachable.com/path" +      url2 = "http://domain.com/path" + +      Instances.set_consistently_unreachable(host) +      Instances.set_unreachable(url1) + +      result = Instances.filter_reachable([host, url1, url2, nil]) +      %{result: result, url1: url1, url2: url2} +    end + +    test "returns a map with keys containing 'not marked consistently unreachable' elements of supplied list", +         %{result: result, url1: url1, url2: url2} do +      assert is_map(result) +      assert Enum.sort([url1, url2]) == result |> Map.keys() |> Enum.sort() +    end + +    test "returns a map with `unreachable_since` values for keys", +         %{result: result, url1: url1, url2: url2} do +      assert is_map(result) +      assert %NaiveDateTime{} = result[url1] +      assert is_nil(result[url2]) +    end + +    test "returns an empty map for empty list or list containing no hosts / url" do +      assert %{} == Instances.filter_reachable([]) +      assert %{} == Instances.filter_reachable([nil]) +    end +  end + +  describe "set_reachable/1" do +    test "sets unreachable url or host reachable" do +      host = "domain.com" +      Instances.set_consistently_unreachable(host) +      refute Instances.reachable?(host) + +      Instances.set_reachable(host) +      assert Instances.reachable?(host) +    end + +    test "keeps reachable url or host reachable" do +      url = "https://site.name?q=" +      assert Instances.reachable?(url) + +      Instances.set_reachable(url) +      assert Instances.reachable?(url) +    end + +    test "returns error status on non-binary input" do +      assert {:error, _} = Instances.set_reachable(nil) +      assert {:error, _} = Instances.set_reachable(1) +    end +  end + +  # Note: implementation-specific (e.g. Instance) details of set_unreachable/1 +  # should be tested in implementation-specific tests +  describe "set_unreachable/1" do +    test "returns error status on non-binary input" do +      assert {:error, _} = Instances.set_unreachable(nil) +      assert {:error, _} = Instances.set_unreachable(1) +    end +  end + +  describe "set_consistently_unreachable/1" do +    test "sets reachable url or host unreachable" do +      url = "http://domain.com?q=" +      assert Instances.reachable?(url) + +      Instances.set_consistently_unreachable(url) +      refute Instances.reachable?(url) +    end + +    test "keeps unreachable url or host unreachable" do +      host = "site.name" +      Instances.set_consistently_unreachable(host) +      refute Instances.reachable?(host) + +      Instances.set_consistently_unreachable(host) +      refute Instances.reachable?(host) +    end +  end +end diff --git a/test/web/mastodon_api/account_view_test.exs b/test/web/mastodon_api/account_view_test.exs index f8cd68173..d7487bed9 100644 --- a/test/web/mastodon_api/account_view_test.exs +++ b/test/web/mastodon_api/account_view_test.exs @@ -5,8 +5,8 @@  defmodule Pleroma.Web.MastodonAPI.AccountViewTest do    use Pleroma.DataCase    import Pleroma.Factory -  alias Pleroma.Web.MastodonAPI.AccountView    alias Pleroma.User +  alias Pleroma.Web.MastodonAPI.AccountView    test "Represent a user account" do      source_data = %{ @@ -63,13 +63,28 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do          confirmation_pending: false,          tags: [],          is_admin: false, -        is_moderator: false +        is_moderator: false, +        relationship: %{}        }      }      assert expected == AccountView.render("account.json", %{user: user})    end +  test "Represent the user account for the account owner" do +    user = insert(:user) + +    notification_settings = %{ +      "remote" => true, +      "local" => true, +      "followers" => true, +      "follows" => true +    } + +    assert %{pleroma: %{notification_settings: ^notification_settings}} = +             AccountView.render("account.json", %{user: user, for: user}) +  end +    test "Represent a Service(bot) account" do      user =        insert(:user, %{ @@ -106,7 +121,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do          confirmation_pending: false,          tags: [],          is_admin: false, -        is_moderator: false +        is_moderator: false, +        relationship: %{}        }      } @@ -140,12 +156,74 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do        blocking: true,        muting: false,        muting_notifications: false, +      subscribing: false,        requested: false,        domain_blocking: false, -      showing_reblogs: false, +      showing_reblogs: true,        endorsed: false      }      assert expected == AccountView.render("relationship.json", %{user: user, target: other_user})    end + +  test "represent an embedded relationship" do +    user = +      insert(:user, %{ +        info: %{note_count: 5, follower_count: 3, source_data: %{"type" => "Service"}}, +        nickname: "shp@shitposter.club", +        inserted_at: ~N[2017-08-15 15:47:06.597036] +      }) + +    other_user = insert(:user) + +    {:ok, other_user} = User.follow(other_user, user) +    {:ok, other_user} = User.block(other_user, user) + +    expected = %{ +      id: to_string(user.id), +      username: "shp", +      acct: user.nickname, +      display_name: user.name, +      locked: false, +      created_at: "2017-08-15T15:47:06.000Z", +      followers_count: 3, +      following_count: 0, +      statuses_count: 5, +      note: user.bio, +      url: user.ap_id, +      avatar: "http://localhost:4001/images/avi.png", +      avatar_static: "http://localhost:4001/images/avi.png", +      header: "http://localhost:4001/images/banner.png", +      header_static: "http://localhost:4001/images/banner.png", +      emojis: [], +      fields: [], +      bot: true, +      source: %{ +        note: "", +        privacy: "public", +        sensitive: false +      }, +      pleroma: %{ +        confirmation_pending: false, +        tags: [], +        is_admin: false, +        is_moderator: false, +        relationship: %{ +          id: to_string(user.id), +          following: false, +          followed_by: false, +          blocking: true, +          subscribing: false, +          muting: false, +          muting_notifications: false, +          requested: false, +          domain_blocking: false, +          showing_reblogs: true, +          endorsed: false +        } +      } +    } + +    assert expected == AccountView.render("account.json", %{user: user, for: other_user}) +  end  end diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index b8f901e6c..a906c6082 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -5,12 +5,20 @@  defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do    use Pleroma.Web.ConnCase -  alias Pleroma.Web.TwitterAPI.TwitterAPI -  alias Pleroma.{Repo, User, Object, Activity, Notification} -  alias Pleroma.Web.{OStatus, CommonAPI} +  alias Ecto.Changeset +  alias Pleroma.Activity +  alias Pleroma.Notification +  alias Pleroma.Object +  alias Pleroma.Repo +  alias Pleroma.ScheduledActivity +  alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub +  alias Pleroma.Web.CommonAPI    alias Pleroma.Web.MastodonAPI.FilterView -  alias Ecto.Changeset +  alias Pleroma.Web.OAuth.App +  alias Pleroma.Web.OStatus +  alias Pleroma.Web.Push +  alias Pleroma.Web.TwitterAPI.TwitterAPI    import Pleroma.Factory    import ExUnit.CaptureLog    import Tesla.Mock @@ -31,7 +39,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        |> assign(:user, user)        |> get("/api/v1/timelines/home") -    assert length(json_response(conn, 200)) == 0 +    assert Enum.empty?(json_response(conn, 200))      {:ok, user} = User.follow(user, following) @@ -94,7 +102,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} =               json_response(conn_one, 200) -    assert Repo.get(Activity, id) +    assert Activity.get_by_id(id)      conn_two =        conn @@ -133,7 +141,72 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        |> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})      assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200) -    assert Repo.get(Activity, id) +    assert Activity.get_by_id(id) +  end + +  test "posting a fake status", %{conn: conn} do +    user = insert(:user) + +    real_conn = +      conn +      |> assign(:user, user) +      |> post("/api/v1/statuses", %{ +        "status" => +          "\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it" +      }) + +    real_status = json_response(real_conn, 200) + +    assert real_status +    assert Object.get_by_ap_id(real_status["uri"]) + +    real_status = +      real_status +      |> Map.put("id", nil) +      |> Map.put("url", nil) +      |> Map.put("uri", nil) +      |> Map.put("created_at", nil) +      |> Kernel.put_in(["pleroma", "conversation_id"], nil) + +    fake_conn = +      conn +      |> assign(:user, user) +      |> post("/api/v1/statuses", %{ +        "status" => +          "\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it", +        "preview" => true +      }) + +    fake_status = json_response(fake_conn, 200) + +    assert fake_status +    refute Object.get_by_ap_id(fake_status["uri"]) + +    fake_status = +      fake_status +      |> Map.put("id", nil) +      |> Map.put("url", nil) +      |> Map.put("uri", nil) +      |> Map.put("created_at", nil) +      |> Kernel.put_in(["pleroma", "conversation_id"], nil) + +    assert real_status == fake_status +  end + +  test "posting a status with OGP link preview", %{conn: conn} do +    Pleroma.Config.put([:rich_media, :enabled], true) +    user = insert(:user) + +    conn = +      conn +      |> assign(:user, user) +      |> post("/api/v1/statuses", %{ +        "status" => "http://example.com/ogp" +      }) + +    assert %{"id" => id, "card" => %{"title" => "The Rock"}} = json_response(conn, 200) +    assert Activity.get_by_id(id) +    Pleroma.Config.put([:rich_media, :enabled], false)    end    test "posting a direct status", %{conn: conn} do @@ -147,7 +220,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        |> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"})      assert %{"id" => id, "visibility" => "direct"} = json_response(conn, 200) -    assert activity = Repo.get(Activity, id) +    assert activity = Activity.get_by_id(id)      assert activity.recipients == [user2.ap_id, user1.ap_id]      assert activity.data["to"] == [user2.ap_id]      assert activity.data["cc"] == [] @@ -227,6 +300,33 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      assert status["url"] != direct.data["id"]    end +  test "doesn't include DMs from blocked users", %{conn: conn} do +    blocker = insert(:user) +    blocked = insert(:user) +    user = insert(:user) +    {:ok, blocker} = User.block(blocker, blocked) + +    {:ok, _blocked_direct} = +      CommonAPI.post(blocked, %{ +        "status" => "Hi @#{blocker.nickname}!", +        "visibility" => "direct" +      }) + +    {:ok, direct} = +      CommonAPI.post(user, %{ +        "status" => "Hi @#{blocker.nickname}!", +        "visibility" => "direct" +      }) + +    res_conn = +      conn +      |> assign(:user, user) +      |> get("api/v1/timelines/direct") + +    [status] = json_response(res_conn, 200) +    assert status["id"] == direct.id +  end +    test "replying to a status", %{conn: conn} do      user = insert(:user) @@ -239,7 +339,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      assert %{"content" => "xD", "id" => id} = json_response(conn, 200) -    activity = Repo.get(Activity, id) +    activity = Activity.get_by_id(id)      assert activity.data["context"] == replied_to.data["context"]      assert activity.data["object"]["inReplyToStatusId"] == replied_to.id @@ -255,7 +355,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      assert %{"content" => "xD", "id" => id} = json_response(conn, 200) -    activity = Repo.get(Activity, id) +    activity = Activity.get_by_id(id)      assert activity    end @@ -284,6 +384,53 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      assert id == to_string(user.id)    end +  test "apps/verify_credentials", %{conn: conn} do +    token = insert(:oauth_token) + +    conn = +      conn +      |> assign(:user, token.user) +      |> assign(:token, token) +      |> get("/api/v1/apps/verify_credentials") + +    app = Repo.preload(token, :app).app + +    expected = %{ +      "name" => app.client_name, +      "website" => app.website, +      "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key) +    } + +    assert expected == json_response(conn, 200) +  end + +  test "creates an oauth app", %{conn: conn} do +    user = insert(:user) +    app_attrs = build(:oauth_app) + +    conn = +      conn +      |> assign(:user, user) +      |> post("/api/v1/apps", %{ +        client_name: app_attrs.client_name, +        redirect_uris: app_attrs.redirect_uris +      }) + +    [app] = Repo.all(App) + +    expected = %{ +      "name" => app.client_name, +      "website" => app.website, +      "client_id" => app.client_id, +      "client_secret" => app.client_secret, +      "id" => app.id |> to_string(), +      "redirect_uri" => app.redirect_uris, +      "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key) +    } + +    assert expected == json_response(conn, 200) +  end +    test "get a status", %{conn: conn} do      activity = insert(:note_activity) @@ -307,7 +454,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        assert %{} = json_response(conn, 200) -      refute Repo.get(Activity, activity.id) +      refute Activity.get_by_id(activity.id)      end      test "when you didn't create it", %{conn: conn} do @@ -321,7 +468,31 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        assert %{"error" => _} = json_response(conn, 403) -      assert Repo.get(Activity, activity.id) == activity +      assert Activity.get_by_id(activity.id) == activity +    end + +    test "when you're an admin or moderator", %{conn: conn} do +      activity1 = insert(:note_activity) +      activity2 = insert(:note_activity) +      admin = insert(:user, info: %{is_admin: true}) +      moderator = insert(:user, info: %{is_moderator: true}) + +      res_conn = +        conn +        |> assign(:user, admin) +        |> delete("/api/v1/statuses/#{activity1.id}") + +      assert %{} = json_response(res_conn, 200) + +      res_conn = +        conn +        |> assign(:user, moderator) +        |> delete("/api/v1/statuses/#{activity2.id}") + +      assert %{} = json_response(res_conn, 200) + +      refute Activity.get_by_id(activity1.id) +      refute Activity.get_by_id(activity2.id)      end    end @@ -683,6 +854,148 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        assert all = json_response(conn, 200)        assert all == []      end + +    test "paginates notifications using min_id, since_id, max_id, and limit", %{conn: conn} do +      user = insert(:user) +      other_user = insert(:user) + +      {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) +      {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) +      {:ok, activity3} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) +      {:ok, activity4} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) + +      notification1_id = Repo.get_by(Notification, activity_id: activity1.id).id |> to_string() +      notification2_id = Repo.get_by(Notification, activity_id: activity2.id).id |> to_string() +      notification3_id = Repo.get_by(Notification, activity_id: activity3.id).id |> to_string() +      notification4_id = Repo.get_by(Notification, activity_id: activity4.id).id |> to_string() + +      conn = +        conn +        |> assign(:user, user) + +      # min_id +      conn_res = +        conn +        |> get("/api/v1/notifications?limit=2&min_id=#{notification1_id}") + +      result = json_response(conn_res, 200) +      assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result + +      # since_id +      conn_res = +        conn +        |> get("/api/v1/notifications?limit=2&since_id=#{notification1_id}") + +      result = json_response(conn_res, 200) +      assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result + +      # max_id +      conn_res = +        conn +        |> get("/api/v1/notifications?limit=2&max_id=#{notification4_id}") + +      result = json_response(conn_res, 200) +      assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result +    end + +    test "filters notifications using exclude_types", %{conn: conn} do +      user = insert(:user) +      other_user = insert(:user) + +      {:ok, mention_activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"}) +      {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"}) +      {:ok, favorite_activity, _} = CommonAPI.favorite(create_activity.id, other_user) +      {:ok, reblog_activity, _} = CommonAPI.repeat(create_activity.id, other_user) +      {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user) + +      mention_notification_id = +        Repo.get_by(Notification, activity_id: mention_activity.id).id |> to_string() + +      favorite_notification_id = +        Repo.get_by(Notification, activity_id: favorite_activity.id).id |> to_string() + +      reblog_notification_id = +        Repo.get_by(Notification, activity_id: reblog_activity.id).id |> to_string() + +      follow_notification_id = +        Repo.get_by(Notification, activity_id: follow_activity.id).id |> to_string() + +      conn = +        conn +        |> assign(:user, user) + +      conn_res = +        get(conn, "/api/v1/notifications", %{exclude_types: ["mention", "favourite", "reblog"]}) + +      assert [%{"id" => ^follow_notification_id}] = json_response(conn_res, 200) + +      conn_res = +        get(conn, "/api/v1/notifications", %{exclude_types: ["favourite", "reblog", "follow"]}) + +      assert [%{"id" => ^mention_notification_id}] = json_response(conn_res, 200) + +      conn_res = +        get(conn, "/api/v1/notifications", %{exclude_types: ["reblog", "follow", "mention"]}) + +      assert [%{"id" => ^favorite_notification_id}] = json_response(conn_res, 200) + +      conn_res = +        get(conn, "/api/v1/notifications", %{exclude_types: ["follow", "mention", "favourite"]}) + +      assert [%{"id" => ^reblog_notification_id}] = json_response(conn_res, 200) +    end + +    test "destroy multiple", %{conn: conn} do +      user = insert(:user) +      other_user = insert(:user) + +      {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) +      {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) +      {:ok, activity3} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"}) +      {:ok, activity4} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"}) + +      notification1_id = Repo.get_by(Notification, activity_id: activity1.id).id |> to_string() +      notification2_id = Repo.get_by(Notification, activity_id: activity2.id).id |> to_string() +      notification3_id = Repo.get_by(Notification, activity_id: activity3.id).id |> to_string() +      notification4_id = Repo.get_by(Notification, activity_id: activity4.id).id |> to_string() + +      conn = +        conn +        |> assign(:user, user) + +      conn_res = +        conn +        |> get("/api/v1/notifications") + +      result = json_response(conn_res, 200) +      assert [%{"id" => ^notification2_id}, %{"id" => ^notification1_id}] = result + +      conn2 = +        conn +        |> assign(:user, other_user) + +      conn_res = +        conn2 +        |> get("/api/v1/notifications") + +      result = json_response(conn_res, 200) +      assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result + +      conn_destroy = +        conn +        |> delete("/api/v1/notifications/destroy_multiple", %{ +          "ids" => [notification1_id, notification2_id] +        }) + +      assert json_response(conn_destroy, 200) == %{} + +      conn_res = +        conn2 +        |> get("/api/v1/notifications") + +      result = json_response(conn_res, 200) +      assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result +    end    end    describe "reblogging" do @@ -901,8 +1214,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        {:ok, _activity} = ActivityPub.follow(other_user, user) -      user = Repo.get(User, user.id) -      other_user = Repo.get(User, other_user.id) +      user = User.get_by_id(user.id) +      other_user = User.get_by_id(other_user.id)        assert User.following?(other_user, user) == false @@ -916,13 +1229,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      end      test "/api/v1/follow_requests/:id/authorize works" do -      user = insert(:user, %{info: %Pleroma.User.Info{locked: true}}) +      user = insert(:user, %{info: %User.Info{locked: true}})        other_user = insert(:user)        {:ok, _activity} = ActivityPub.follow(other_user, user) -      user = Repo.get(User, user.id) -      other_user = Repo.get(User, other_user.id) +      user = User.get_by_id(user.id) +      other_user = User.get_by_id(other_user.id)        assert User.following?(other_user, user) == false @@ -934,8 +1247,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        assert relationship = json_response(conn, 200)        assert to_string(other_user.id) == relationship["id"] -      user = Repo.get(User, user.id) -      other_user = Repo.get(User, other_user.id) +      user = User.get_by_id(user.id) +      other_user = User.get_by_id(other_user.id)        assert User.following?(other_user, user) == true      end @@ -958,6 +1271,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        {:ok, _activity} = ActivityPub.follow(other_user, user) +      user = User.get_by_id(user.id) +        conn =          build_conn()          |> assign(:user, user) @@ -966,8 +1281,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        assert relationship = json_response(conn, 200)        assert to_string(other_user.id) == relationship["id"] -      user = Repo.get(User, user.id) -      other_user = Repo.get(User, other_user.id) +      user = User.get_by_id(user.id) +      other_user = User.get_by_id(other_user.id)        assert User.following?(other_user, user) == false      end @@ -990,6 +1305,17 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      assert %{"error" => "Can't find user"} = json_response(conn, 404)    end +  test "account fetching also works nickname", %{conn: conn} do +    user = insert(:user) + +    conn = +      conn +      |> get("/api/v1/accounts/#{user.nickname}") + +    assert %{"id" => id} = json_response(conn, 200) +    assert id == user.id +  end +    test "media upload", %{conn: conn} do      file = %Plug.Upload{        content_type: "image/jpg", @@ -1085,9 +1411,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      assert id == to_string(user.id)    end -  test "getting followers, hide_network", %{conn: conn} do +  test "getting followers, hide_followers", %{conn: conn} do      user = insert(:user) -    other_user = insert(:user, %{info: %{hide_network: true}}) +    other_user = insert(:user, %{info: %{hide_followers: true}})      {:ok, _user} = User.follow(user, other_user)      conn = @@ -1097,9 +1423,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      assert [] == json_response(conn, 200)    end -  test "getting followers, hide_network, same user requesting", %{conn: conn} do +  test "getting followers, hide_followers, same user requesting", %{conn: conn} do      user = insert(:user) -    other_user = insert(:user, %{info: %{hide_network: true}}) +    other_user = insert(:user, %{info: %{hide_followers: true}})      {:ok, _user} = User.follow(user, other_user)      conn = @@ -1110,6 +1436,47 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      refute [] == json_response(conn, 200)    end +  test "getting followers, pagination", %{conn: conn} do +    user = insert(:user) +    follower1 = insert(:user) +    follower2 = insert(:user) +    follower3 = insert(:user) +    {:ok, _} = User.follow(follower1, user) +    {:ok, _} = User.follow(follower2, user) +    {:ok, _} = User.follow(follower3, user) + +    conn = +      conn +      |> assign(:user, user) + +    res_conn = +      conn +      |> get("/api/v1/accounts/#{user.id}/followers?since_id=#{follower1.id}") + +    assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200) +    assert id3 == follower3.id +    assert id2 == follower2.id + +    res_conn = +      conn +      |> get("/api/v1/accounts/#{user.id}/followers?max_id=#{follower3.id}") + +    assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200) +    assert id2 == follower2.id +    assert id1 == follower1.id + +    res_conn = +      conn +      |> get("/api/v1/accounts/#{user.id}/followers?limit=1&max_id=#{follower3.id}") + +    assert [%{"id" => id2}] = json_response(res_conn, 200) +    assert id2 == follower2.id + +    assert [link_header] = get_resp_header(res_conn, "link") +    assert link_header =~ ~r/since_id=#{follower2.id}/ +    assert link_header =~ ~r/max_id=#{follower2.id}/ +  end +    test "getting following", %{conn: conn} do      user = insert(:user)      other_user = insert(:user) @@ -1123,8 +1490,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      assert id == to_string(other_user.id)    end -  test "getting following, hide_network", %{conn: conn} do -    user = insert(:user, %{info: %{hide_network: true}}) +  test "getting following, hide_follows", %{conn: conn} do +    user = insert(:user, %{info: %{hide_follows: true}})      other_user = insert(:user)      {:ok, user} = User.follow(user, other_user) @@ -1135,8 +1502,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      assert [] == json_response(conn, 200)    end -  test "getting following, hide_network, same user requesting", %{conn: conn} do -    user = insert(:user, %{info: %{hide_network: true}}) +  test "getting following, hide_follows, same user requesting", %{conn: conn} do +    user = insert(:user, %{info: %{hide_follows: true}})      other_user = insert(:user)      {:ok, user} = User.follow(user, other_user) @@ -1148,6 +1515,47 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      refute [] == json_response(conn, 200)    end +  test "getting following, pagination", %{conn: conn} do +    user = insert(:user) +    following1 = insert(:user) +    following2 = insert(:user) +    following3 = insert(:user) +    {:ok, _} = User.follow(user, following1) +    {:ok, _} = User.follow(user, following2) +    {:ok, _} = User.follow(user, following3) + +    conn = +      conn +      |> assign(:user, user) + +    res_conn = +      conn +      |> get("/api/v1/accounts/#{user.id}/following?since_id=#{following1.id}") + +    assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200) +    assert id3 == following3.id +    assert id2 == following2.id + +    res_conn = +      conn +      |> get("/api/v1/accounts/#{user.id}/following?max_id=#{following3.id}") + +    assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200) +    assert id2 == following2.id +    assert id1 == following1.id + +    res_conn = +      conn +      |> get("/api/v1/accounts/#{user.id}/following?limit=1&max_id=#{following3.id}") + +    assert [%{"id" => id2}] = json_response(res_conn, 200) +    assert id2 == following2.id + +    assert [link_header] = get_resp_header(res_conn, "link") +    assert link_header =~ ~r/since_id=#{following2.id}/ +    assert link_header =~ ~r/max_id=#{following2.id}/ +  end +    test "following / unfollowing a user", %{conn: conn} do      user = insert(:user)      other_user = insert(:user) @@ -1159,7 +1567,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      assert %{"id" => _id, "following" => true} = json_response(conn, 200) -    user = Repo.get(User, user.id) +    user = User.get_by_id(user.id)      conn =        build_conn() @@ -1168,7 +1576,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      assert %{"id" => _id, "following" => false} = json_response(conn, 200) -    user = Repo.get(User, user.id) +    user = User.get_by_id(user.id)      conn =        build_conn() @@ -1179,6 +1587,61 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      assert id == to_string(other_user.id)    end +  test "muting / unmuting a user", %{conn: conn} do +    user = insert(:user) +    other_user = insert(:user) + +    conn = +      conn +      |> assign(:user, user) +      |> post("/api/v1/accounts/#{other_user.id}/mute") + +    assert %{"id" => _id, "muting" => true} = json_response(conn, 200) + +    user = User.get_by_id(user.id) + +    conn = +      build_conn() +      |> assign(:user, user) +      |> post("/api/v1/accounts/#{other_user.id}/unmute") + +    assert %{"id" => _id, "muting" => false} = json_response(conn, 200) +  end + +  test "subscribing / unsubscribing to a user", %{conn: conn} do +    user = insert(:user) +    subscription_target = insert(:user) + +    conn = +      conn +      |> assign(:user, user) +      |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/subscribe") + +    assert %{"id" => _id, "subscribing" => true} = json_response(conn, 200) + +    conn = +      build_conn() +      |> assign(:user, user) +      |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/unsubscribe") + +    assert %{"id" => _id, "subscribing" => false} = json_response(conn, 200) +  end + +  test "getting a list of mutes", %{conn: conn} do +    user = insert(:user) +    other_user = insert(:user) + +    {:ok, user} = User.mute(user, other_user) + +    conn = +      conn +      |> assign(:user, user) +      |> get("/api/v1/mutes") + +    other_user_id = to_string(other_user.id) +    assert [%{"id" => ^other_user_id}] = json_response(conn, 200) +  end +    test "blocking / unblocking a user", %{conn: conn} do      user = insert(:user)      other_user = insert(:user) @@ -1190,7 +1653,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      assert %{"id" => _id, "blocking" => true} = json_response(conn, 200) -    user = Repo.get(User, user.id) +    user = User.get_by_id(user.id)      conn =        build_conn() @@ -1255,26 +1718,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      assert "even.worse.site" in domain_blocks    end -  test "unimplemented mute endpoints" do +  test "unimplemented follow_requests, blocks, domain blocks" do      user = insert(:user) -    other_user = insert(:user) -    ["mute", "unmute"] -    |> Enum.each(fn endpoint -> -      conn = -        build_conn() -        |> assign(:user, user) -        |> post("/api/v1/accounts/#{other_user.id}/#{endpoint}") - -      assert %{"id" => id} = json_response(conn, 200) -      assert id == to_string(other_user.id) -    end) -  end - -  test "unimplemented mutes, follow_requests, blocks, domain blocks" do -    user = insert(:user) - -    ["blocks", "domain_blocks", "mutes", "follow_requests"] +    ["blocks", "domain_blocks", "follow_requests"]      |> Enum.each(fn endpoint ->        conn =          build_conn() @@ -1445,9 +1892,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        assert user = json_response(conn, 200)        assert user["note"] == -               "I drink <a class=\"hashtag\" data-tag=\"cofe\" href=\"http://localhost:4001/tag/cofe\">#cofe</a> with <span class=\"h-card\"><a data-user=\"#{ -                 user2.id -               }\" class=\"u-url mention\" href=\"#{user2.ap_id}\">@<span>#{user2.nickname}</span></a></span>" +               ~s(I drink <a class="hashtag" data-tag="cofe" href="http://localhost:4001/tag/cofe" rel="tag">#cofe</a> with <span class="h-card"><a data-user=") <> +                 user2.id <> +                 ~s(" class="u-url mention" href=") <> +                 user2.ap_id <> ~s(">@<span>) <> user2.nickname <> ~s(</span></a></span>)      end      test "updates the user's locking status", %{conn: conn} do @@ -1509,9 +1957,48 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        assert user_response = json_response(conn, 200)        assert user_response["header"] != User.banner_url(user)      end + +    test "requires 'write' permission", %{conn: conn} do +      token1 = insert(:oauth_token, scopes: ["read"]) +      token2 = insert(:oauth_token, scopes: ["write", "follow"]) + +      for token <- [token1, token2] do +        conn = +          conn +          |> put_req_header("authorization", "Bearer #{token.token}") +          |> patch("/api/v1/accounts/update_credentials", %{}) + +        if token == token1 do +          assert %{"error" => "Insufficient permissions: write."} == json_response(conn, 403) +        else +          assert json_response(conn, 200) +        end +      end +    end    end    test "get instance information", %{conn: conn} do +    conn = get(conn, "/api/v1/instance") +    assert result = json_response(conn, 200) + +    # Note: not checking for "max_toot_chars" since it's optional +    assert %{ +             "uri" => _, +             "title" => _, +             "description" => _, +             "version" => _, +             "email" => _, +             "urls" => %{ +               "streaming_api" => _ +             }, +             "stats" => _, +             "thumbnail" => _, +             "languages" => _, +             "registrations" => _ +           } = result +  end + +  test "get instance stats", %{conn: conn} do      user = insert(:user, %{local: true})      user2 = insert(:user, %{local: true}) @@ -1523,7 +2010,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      {:ok, _} = TwitterAPI.create_status(user, %{"status" => "cofe"})      # Stats should count users with missing or nil `info.deactivated` value -    user = Repo.get(User, user.id) +    user = User.get_by_id(user.id)      info_change = Changeset.change(user.info, %{deactivated: nil})      {:ok, _user} = @@ -1653,6 +2140,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      end      test "Status rich-media Card", %{conn: conn, user: user} do +      Pleroma.Config.put([:rich_media, :enabled], true)        {:ok, activity} = CommonAPI.post(user, %{"status" => "http://example.com/ogp"})        response = @@ -1663,10 +2151,603 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        assert response == %{                 "image" => "http://ia.media-imdb.com/images/rock.jpg",                 "provider_name" => "www.imdb.com", +               "provider_url" => "http://www.imdb.com",                 "title" => "The Rock",                 "type" => "link", -               "url" => "http://www.imdb.com/title/tt0117500/" +               "url" => "http://www.imdb.com/title/tt0117500/", +               "description" => nil, +               "pleroma" => %{ +                 "opengraph" => %{ +                   "image" => "http://ia.media-imdb.com/images/rock.jpg", +                   "title" => "The Rock", +                   "type" => "video.movie", +                   "url" => "http://www.imdb.com/title/tt0117500/" +                 } +               }               } + +      # works with private posts +      {:ok, activity} = +        CommonAPI.post(user, %{"status" => "http://example.com/ogp", "visibility" => "direct"}) + +      response_two = +        conn +        |> assign(:user, user) +        |> get("/api/v1/statuses/#{activity.id}/card") +        |> json_response(200) + +      assert response_two == response + +      Pleroma.Config.put([:rich_media, :enabled], false) +    end +  end + +  test "bookmarks" do +    user = insert(:user) +    for_user = insert(:user) + +    {:ok, activity1} = +      CommonAPI.post(user, %{ +        "status" => "heweoo?" +      }) + +    {:ok, activity2} = +      CommonAPI.post(user, %{ +        "status" => "heweoo!" +      }) + +    response1 = +      build_conn() +      |> assign(:user, for_user) +      |> post("/api/v1/statuses/#{activity1.id}/bookmark") + +    assert json_response(response1, 200)["bookmarked"] == true + +    response2 = +      build_conn() +      |> assign(:user, for_user) +      |> post("/api/v1/statuses/#{activity2.id}/bookmark") + +    assert json_response(response2, 200)["bookmarked"] == true + +    bookmarks = +      build_conn() +      |> assign(:user, for_user) +      |> get("/api/v1/bookmarks") + +    assert [json_response(response2, 200), json_response(response1, 200)] == +             json_response(bookmarks, 200) + +    response1 = +      build_conn() +      |> assign(:user, for_user) +      |> post("/api/v1/statuses/#{activity1.id}/unbookmark") + +    assert json_response(response1, 200)["bookmarked"] == false + +    bookmarks = +      build_conn() +      |> assign(:user, for_user) +      |> get("/api/v1/bookmarks") + +    assert [json_response(response2, 200)] == json_response(bookmarks, 200) +  end + +  describe "conversation muting" do +    setup do +      user = insert(:user) +      {:ok, activity} = CommonAPI.post(user, %{"status" => "HIE"}) + +      [user: user, activity: activity] +    end + +    test "mute conversation", %{conn: conn, user: user, activity: activity} do +      id_str = to_string(activity.id) + +      assert %{"id" => ^id_str, "muted" => true} = +               conn +               |> assign(:user, user) +               |> post("/api/v1/statuses/#{activity.id}/mute") +               |> json_response(200) +    end + +    test "unmute conversation", %{conn: conn, user: user, activity: activity} do +      {:ok, _} = CommonAPI.add_mute(user, activity) + +      id_str = to_string(activity.id) +      user = refresh_record(user) + +      assert %{"id" => ^id_str, "muted" => false} = +               conn +               |> assign(:user, user) +               |> post("/api/v1/statuses/#{activity.id}/unmute") +               |> json_response(200) +    end +  end + +  test "flavours switching (Pleroma Extension)", %{conn: conn} do +    user = insert(:user) + +    get_old_flavour = +      conn +      |> assign(:user, user) +      |> get("/api/v1/pleroma/flavour") + +    assert "glitch" == json_response(get_old_flavour, 200) + +    set_flavour = +      conn +      |> assign(:user, user) +      |> post("/api/v1/pleroma/flavour/vanilla") + +    assert "vanilla" == json_response(set_flavour, 200) + +    get_new_flavour = +      conn +      |> assign(:user, user) +      |> post("/api/v1/pleroma/flavour/vanilla") + +    assert json_response(set_flavour, 200) == json_response(get_new_flavour, 200) +  end + +  describe "reports" do +    setup do +      reporter = insert(:user) +      target_user = insert(:user) + +      {:ok, activity} = CommonAPI.post(target_user, %{"status" => "foobar"}) + +      [reporter: reporter, target_user: target_user, activity: activity] +    end + +    test "submit a basic report", %{conn: conn, reporter: reporter, target_user: target_user} do +      assert %{"action_taken" => false, "id" => _} = +               conn +               |> assign(:user, reporter) +               |> post("/api/v1/reports", %{"account_id" => target_user.id}) +               |> json_response(200) +    end + +    test "submit a report with statuses and comment", %{ +      conn: conn, +      reporter: reporter, +      target_user: target_user, +      activity: activity +    } do +      assert %{"action_taken" => false, "id" => _} = +               conn +               |> assign(:user, reporter) +               |> post("/api/v1/reports", %{ +                 "account_id" => target_user.id, +                 "status_ids" => [activity.id], +                 "comment" => "bad status!" +               }) +               |> json_response(200) +    end + +    test "account_id is required", %{ +      conn: conn, +      reporter: reporter, +      activity: activity +    } do +      assert %{"error" => "Valid `account_id` required"} = +               conn +               |> assign(:user, reporter) +               |> post("/api/v1/reports", %{"status_ids" => [activity.id]}) +               |> json_response(400) +    end + +    test "comment must be up to the size specified in the config", %{ +      conn: conn, +      reporter: reporter, +      target_user: target_user +    } do +      max_size = Pleroma.Config.get([:instance, :max_report_comment_size], 1000) +      comment = String.pad_trailing("a", max_size + 1, "a") + +      error = %{"error" => "Comment must be up to #{max_size} characters"} + +      assert ^error = +               conn +               |> assign(:user, reporter) +               |> post("/api/v1/reports", %{"account_id" => target_user.id, "comment" => comment}) +               |> json_response(400) +    end +  end + +  describe "link headers" do +    test "preserves parameters in link headers", %{conn: conn} do +      user = insert(:user) +      other_user = insert(:user) + +      {:ok, activity1} = +        CommonAPI.post(other_user, %{ +          "status" => "hi @#{user.nickname}", +          "visibility" => "public" +        }) + +      {:ok, activity2} = +        CommonAPI.post(other_user, %{ +          "status" => "hi @#{user.nickname}", +          "visibility" => "public" +        }) + +      notification1 = Repo.get_by(Notification, activity_id: activity1.id) +      notification2 = Repo.get_by(Notification, activity_id: activity2.id) + +      conn = +        conn +        |> assign(:user, user) +        |> get("/api/v1/notifications", %{media_only: true}) + +      assert [link_header] = get_resp_header(conn, "link") +      assert link_header =~ ~r/media_only=true/ +      assert link_header =~ ~r/since_id=#{notification2.id}/ +      assert link_header =~ ~r/max_id=#{notification1.id}/ +    end +  end + +  test "accounts fetches correct account for nicknames beginning with numbers", %{conn: conn} do +    # Need to set an old-style integer ID to reproduce the problem +    # (these are no longer assigned to new accounts but were preserved +    # for existing accounts during the migration to flakeIDs) +    user_one = insert(:user, %{id: 1212}) +    user_two = insert(:user, %{nickname: "#{user_one.id}garbage"}) + +    resp_one = +      conn +      |> get("/api/v1/accounts/#{user_one.id}") + +    resp_two = +      conn +      |> get("/api/v1/accounts/#{user_two.nickname}") + +    resp_three = +      conn +      |> get("/api/v1/accounts/#{user_two.id}") + +    acc_one = json_response(resp_one, 200) +    acc_two = json_response(resp_two, 200) +    acc_three = json_response(resp_three, 200) +    refute acc_one == acc_two +    assert acc_two == acc_three +  end + +  describe "custom emoji" do +    test "with tags", %{conn: conn} do +      [emoji | _body] = +        conn +        |> get("/api/v1/custom_emojis") +        |> json_response(200) + +      assert Map.has_key?(emoji, "shortcode") +      assert Map.has_key?(emoji, "static_url") +      assert Map.has_key?(emoji, "tags") +      assert is_list(emoji["tags"]) +      assert Map.has_key?(emoji, "url") +      assert Map.has_key?(emoji, "visible_in_picker") +    end +  end + +  describe "index/2 redirections" do +    setup %{conn: conn} do +      session_opts = [ +        store: :cookie, +        key: "_test", +        signing_salt: "cooldude" +      ] + +      conn = +        conn +        |> Plug.Session.call(Plug.Session.init(session_opts)) +        |> fetch_session() + +      test_path = "/web/statuses/test" +      %{conn: conn, path: test_path} +    end + +    test "redirects not logged-in users to the login page", %{conn: conn, path: path} do +      conn = get(conn, path) + +      assert conn.status == 302 +      assert redirected_to(conn) == "/web/login" +    end + +    test "does not redirect logged in users to the login page", %{conn: conn, path: path} do +      token = insert(:oauth_token) + +      conn = +        conn +        |> assign(:user, token.user) +        |> put_session(:oauth_token, token.token) +        |> get(path) + +      assert conn.status == 200 +    end + +    test "saves referer path to session", %{conn: conn, path: path} do +      conn = get(conn, path) +      return_to = Plug.Conn.get_session(conn, :return_to) + +      assert return_to == path +    end + +    test "redirects to the saved path after log in", %{conn: conn, path: path} do +      app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".") +      auth = insert(:oauth_authorization, app: app) + +      conn = +        conn +        |> put_session(:return_to, path) +        |> get("/web/login", %{code: auth.token}) + +      assert conn.status == 302 +      assert redirected_to(conn) == path +    end + +    test "redirects to the getting-started page when referer is not present", %{conn: conn} do +      app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".") +      auth = insert(:oauth_authorization, app: app) + +      conn = get(conn, "/web/login", %{code: auth.token}) + +      assert conn.status == 302 +      assert redirected_to(conn) == "/web/getting-started" +    end +  end + +  describe "scheduled activities" do +    test "creates a scheduled activity", %{conn: conn} do +      user = insert(:user) +      scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses", %{ +          "status" => "scheduled", +          "scheduled_at" => scheduled_at +        }) + +      assert %{"scheduled_at" => expected_scheduled_at} = json_response(conn, 200) +      assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(scheduled_at) +      assert [] == Repo.all(Activity) +    end + +    test "creates a scheduled activity with a media attachment", %{conn: conn} do +      user = insert(:user) +      scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond) + +      file = %Plug.Upload{ +        content_type: "image/jpg", +        path: Path.absname("test/fixtures/image.jpg"), +        filename: "an_image.jpg" +      } + +      {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses", %{ +          "media_ids" => [to_string(upload.id)], +          "status" => "scheduled", +          "scheduled_at" => scheduled_at +        }) + +      assert %{"media_attachments" => [media_attachment]} = json_response(conn, 200) +      assert %{"type" => "image"} = media_attachment +    end + +    test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now", +         %{conn: conn} do +      user = insert(:user) + +      scheduled_at = +        NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses", %{ +          "status" => "not scheduled", +          "scheduled_at" => scheduled_at +        }) + +      assert %{"content" => "not scheduled"} = json_response(conn, 200) +      assert [] == Repo.all(ScheduledActivity) +    end + +    test "returns error when daily user limit is exceeded", %{conn: conn} do +      user = insert(:user) + +      today = +        NaiveDateTime.utc_now() +        |> NaiveDateTime.add(:timer.minutes(6), :millisecond) +        |> NaiveDateTime.to_iso8601() + +      attrs = %{params: %{}, scheduled_at: today} +      {:ok, _} = ScheduledActivity.create(user, attrs) +      {:ok, _} = ScheduledActivity.create(user, attrs) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today}) + +      assert %{"error" => "daily limit exceeded"} == json_response(conn, 422) +    end + +    test "returns error when total user limit is exceeded", %{conn: conn} do +      user = insert(:user) + +      today = +        NaiveDateTime.utc_now() +        |> NaiveDateTime.add(:timer.minutes(6), :millisecond) +        |> NaiveDateTime.to_iso8601() + +      tomorrow = +        NaiveDateTime.utc_now() +        |> NaiveDateTime.add(:timer.hours(36), :millisecond) +        |> NaiveDateTime.to_iso8601() + +      attrs = %{params: %{}, scheduled_at: today} +      {:ok, _} = ScheduledActivity.create(user, attrs) +      {:ok, _} = ScheduledActivity.create(user, attrs) +      {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow}) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow}) + +      assert %{"error" => "total limit exceeded"} == json_response(conn, 422) +    end + +    test "shows scheduled activities", %{conn: conn} do +      user = insert(:user) +      scheduled_activity_id1 = insert(:scheduled_activity, user: user).id |> to_string() +      scheduled_activity_id2 = insert(:scheduled_activity, user: user).id |> to_string() +      scheduled_activity_id3 = insert(:scheduled_activity, user: user).id |> to_string() +      scheduled_activity_id4 = insert(:scheduled_activity, user: user).id |> to_string() + +      conn = +        conn +        |> assign(:user, user) + +      # min_id +      conn_res = +        conn +        |> get("/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}") + +      result = json_response(conn_res, 200) +      assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result + +      # since_id +      conn_res = +        conn +        |> get("/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}") + +      result = json_response(conn_res, 200) +      assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result + +      # max_id +      conn_res = +        conn +        |> get("/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}") + +      result = json_response(conn_res, 200) +      assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result +    end + +    test "shows a scheduled activity", %{conn: conn} do +      user = insert(:user) +      scheduled_activity = insert(:scheduled_activity, user: user) + +      res_conn = +        conn +        |> assign(:user, user) +        |> get("/api/v1/scheduled_statuses/#{scheduled_activity.id}") + +      assert %{"id" => scheduled_activity_id} = json_response(res_conn, 200) +      assert scheduled_activity_id == scheduled_activity.id |> to_string() + +      res_conn = +        conn +        |> assign(:user, user) +        |> get("/api/v1/scheduled_statuses/404") + +      assert %{"error" => "Record not found"} = json_response(res_conn, 404) +    end + +    test "updates a scheduled activity", %{conn: conn} do +      user = insert(:user) +      scheduled_activity = insert(:scheduled_activity, user: user) + +      new_scheduled_at = +        NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond) + +      res_conn = +        conn +        |> assign(:user, user) +        |> put("/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{ +          scheduled_at: new_scheduled_at +        }) + +      assert %{"scheduled_at" => expected_scheduled_at} = json_response(res_conn, 200) +      assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at) + +      res_conn = +        conn +        |> assign(:user, user) +        |> put("/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at}) + +      assert %{"error" => "Record not found"} = json_response(res_conn, 404)      end + +    test "deletes a scheduled activity", %{conn: conn} do +      user = insert(:user) +      scheduled_activity = insert(:scheduled_activity, user: user) + +      res_conn = +        conn +        |> assign(:user, user) +        |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}") + +      assert %{} = json_response(res_conn, 200) +      assert nil == Repo.get(ScheduledActivity, scheduled_activity.id) + +      res_conn = +        conn +        |> assign(:user, user) +        |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}") + +      assert %{"error" => "Record not found"} = json_response(res_conn, 404) +    end +  end + +  test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do +    user1 = insert(:user) +    user2 = insert(:user) +    user3 = insert(:user) + +    {:ok, replied_to} = TwitterAPI.create_status(user1, %{"status" => "cofe"}) + +    # Reply to status from another user +    conn1 = +      conn +      |> assign(:user, user2) +      |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id}) + +    assert %{"content" => "xD", "id" => id} = json_response(conn1, 200) + +    activity = Activity.get_by_id(id) + +    assert activity.data["object"]["inReplyTo"] == replied_to.data["object"]["id"] +    assert activity.data["object"]["inReplyToStatusId"] == replied_to.id + +    # Reblog from the third user +    conn2 = +      conn +      |> assign(:user, user3) +      |> post("/api/v1/statuses/#{activity.id}/reblog") + +    assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} = +             json_response(conn2, 200) + +    assert to_string(activity.id) == id + +    # Getting third user status +    conn3 = +      conn +      |> assign(:user, user3) +      |> get("api/v1/timelines/home") + +    [reblogged_activity] = json_response(conn3, 200) + +    assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id + +    replied_to_user = User.get_by_ap_id(replied_to.data["actor"]) +    assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id    end  end diff --git a/test/web/mastodon_api/notification_view_test.exs b/test/web/mastodon_api/notification_view_test.exs new file mode 100644 index 000000000..f2c1eb76c --- /dev/null +++ b/test/web/mastodon_api/notification_view_test.exs @@ -0,0 +1,104 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do +  use Pleroma.DataCase + +  alias Pleroma.Activity +  alias Pleroma.Notification +  alias Pleroma.Repo +  alias Pleroma.User +  alias Pleroma.Web.CommonAPI +  alias Pleroma.Web.CommonAPI.Utils +  alias Pleroma.Web.MastodonAPI.AccountView +  alias Pleroma.Web.MastodonAPI.NotificationView +  alias Pleroma.Web.MastodonAPI.StatusView +  import Pleroma.Factory + +  test "Mention notification" do +    user = insert(:user) +    mentioned_user = insert(:user) +    {:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{mentioned_user.nickname}"}) +    {:ok, [notification]} = Notification.create_notifications(activity) +    user = User.get_by_id(user.id) + +    expected = %{ +      id: to_string(notification.id), +      pleroma: %{is_seen: false}, +      type: "mention", +      account: AccountView.render("account.json", %{user: user, for: mentioned_user}), +      status: StatusView.render("status.json", %{activity: activity, for: mentioned_user}), +      created_at: Utils.to_masto_date(notification.inserted_at) +    } + +    result = +      NotificationView.render("index.json", %{notifications: [notification], for: mentioned_user}) + +    assert [expected] == result +  end + +  test "Favourite notification" do +    user = insert(:user) +    another_user = insert(:user) +    {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"}) +    {:ok, favorite_activity, _object} = CommonAPI.favorite(create_activity.id, another_user) +    {:ok, [notification]} = Notification.create_notifications(favorite_activity) +    create_activity = Activity.get_by_id(create_activity.id) + +    expected = %{ +      id: to_string(notification.id), +      pleroma: %{is_seen: false}, +      type: "favourite", +      account: AccountView.render("account.json", %{user: another_user, for: user}), +      status: StatusView.render("status.json", %{activity: create_activity, for: user}), +      created_at: Utils.to_masto_date(notification.inserted_at) +    } + +    result = NotificationView.render("index.json", %{notifications: [notification], for: user}) + +    assert [expected] == result +  end + +  test "Reblog notification" do +    user = insert(:user) +    another_user = insert(:user) +    {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"}) +    {:ok, reblog_activity, _object} = CommonAPI.repeat(create_activity.id, another_user) +    {:ok, [notification]} = Notification.create_notifications(reblog_activity) +    reblog_activity = Activity.get_by_id(create_activity.id) + +    expected = %{ +      id: to_string(notification.id), +      pleroma: %{is_seen: false}, +      type: "reblog", +      account: AccountView.render("account.json", %{user: another_user, for: user}), +      status: StatusView.render("status.json", %{activity: reblog_activity, for: user}), +      created_at: Utils.to_masto_date(notification.inserted_at) +    } + +    result = NotificationView.render("index.json", %{notifications: [notification], for: user}) + +    assert [expected] == result +  end + +  test "Follow notification" do +    follower = insert(:user) +    followed = insert(:user) +    {:ok, follower, followed, _activity} = CommonAPI.follow(follower, followed) +    notification = Notification |> Repo.one() |> Repo.preload(:activity) + +    expected = %{ +      id: to_string(notification.id), +      pleroma: %{is_seen: false}, +      type: "follow", +      account: AccountView.render("account.json", %{user: follower, for: followed}), +      created_at: Utils.to_masto_date(notification.inserted_at) +    } + +    result = +      NotificationView.render("index.json", %{notifications: [notification], for: followed}) + +    assert [expected] == result +  end +end diff --git a/test/web/mastodon_api/push_subscription_view_test.exs b/test/web/mastodon_api/push_subscription_view_test.exs new file mode 100644 index 000000000..dc935fc82 --- /dev/null +++ b/test/web/mastodon_api/push_subscription_view_test.exs @@ -0,0 +1,23 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.PushSubscriptionViewTest do +  use Pleroma.DataCase +  import Pleroma.Factory +  alias Pleroma.Web.MastodonAPI.PushSubscriptionView, as: View +  alias Pleroma.Web.Push + +  test "Represent a subscription" do +    subscription = insert(:push_subscription, data: %{"alerts" => %{"mention" => true}}) + +    expected = %{ +      alerts: %{"mention" => true}, +      endpoint: subscription.endpoint, +      id: to_string(subscription.id), +      server_key: Keyword.get(Push.vapid_config(), :public_key) +    } + +    assert expected == View.render("push_subscription.json", %{subscription: subscription}) +  end +end diff --git a/test/web/mastodon_api/scheduled_activity_view_test.exs b/test/web/mastodon_api/scheduled_activity_view_test.exs new file mode 100644 index 000000000..ecbb855d4 --- /dev/null +++ b/test/web/mastodon_api/scheduled_activity_view_test.exs @@ -0,0 +1,68 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.ScheduledActivityViewTest do +  use Pleroma.DataCase +  alias Pleroma.ScheduledActivity +  alias Pleroma.Web.ActivityPub.ActivityPub +  alias Pleroma.Web.CommonAPI +  alias Pleroma.Web.CommonAPI.Utils +  alias Pleroma.Web.MastodonAPI.ScheduledActivityView +  alias Pleroma.Web.MastodonAPI.StatusView +  import Pleroma.Factory + +  test "A scheduled activity with a media attachment" do +    user = insert(:user) +    {:ok, activity} = CommonAPI.post(user, %{"status" => "hi"}) + +    scheduled_at = +      NaiveDateTime.utc_now() +      |> NaiveDateTime.add(:timer.minutes(10), :millisecond) +      |> NaiveDateTime.to_iso8601() + +    file = %Plug.Upload{ +      content_type: "image/jpg", +      path: Path.absname("test/fixtures/image.jpg"), +      filename: "an_image.jpg" +    } + +    {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id) + +    attrs = %{ +      params: %{ +        "media_ids" => [upload.id], +        "status" => "hi", +        "sensitive" => true, +        "spoiler_text" => "spoiler", +        "visibility" => "unlisted", +        "in_reply_to_id" => to_string(activity.id) +      }, +      scheduled_at: scheduled_at +    } + +    {:ok, scheduled_activity} = ScheduledActivity.create(user, attrs) +    result = ScheduledActivityView.render("show.json", %{scheduled_activity: scheduled_activity}) + +    expected = %{ +      id: to_string(scheduled_activity.id), +      media_attachments: +        %{"media_ids" => [upload.id]} +        |> Utils.attachments_from_ids() +        |> Enum.map(&StatusView.render("attachment.json", %{attachment: &1})), +      params: %{ +        in_reply_to_id: to_string(activity.id), +        media_ids: [upload.id], +        poll: nil, +        scheduled_at: nil, +        sensitive: true, +        spoiler_text: "spoiler", +        text: "hi", +        visibility: "unlisted" +      }, +      scheduled_at: Utils.to_masto_date(scheduled_activity.scheduled_at) +    } + +    assert expected == result +  end +end diff --git a/test/web/mastodon_api/status_view_test.exs b/test/web/mastodon_api/status_view_test.exs index ebf6273e8..db2fdc2f6 100644 --- a/test/web/mastodon_api/status_view_test.exs +++ b/test/web/mastodon_api/status_view_test.exs @@ -5,12 +5,14 @@  defmodule Pleroma.Web.MastodonAPI.StatusViewTest do    use Pleroma.DataCase -  alias Pleroma.Web.MastodonAPI.{StatusView, AccountView} +  alias Pleroma.Activity    alias Pleroma.User -  alias Pleroma.Web.OStatus -  alias Pleroma.Web.CommonAPI    alias Pleroma.Web.ActivityPub.ActivityPub -  alias Pleroma.Activity +  alias Pleroma.Web.CommonAPI +  alias Pleroma.Web.CommonAPI.Utils +  alias Pleroma.Web.MastodonAPI.AccountView +  alias Pleroma.Web.MastodonAPI.StatusView +  alias Pleroma.Web.OStatus    import Pleroma.Factory    import Tesla.Mock @@ -71,6 +73,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      note = insert(:note_activity)      user = User.get_cached_by_ap_id(note.data["actor"]) +    convo_id = Utils.context_to_conversation_id(note.data["object"]["context"]) +      status = StatusView.render("status.json", %{activity: note})      created_at = @@ -80,10 +84,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      expected = %{        id: to_string(note.id),        uri: note.data["object"]["id"], -      url: note.data["object"]["id"], +      url: Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, note),        account: AccountView.render("account.json", %{user: user}),        in_reply_to_id: nil,        in_reply_to_account_id: nil, +      card: nil,        reblog: nil,        content: HtmlSanitizeEx.basic_html(note.data["object"]["content"]),        created_at: created_at, @@ -91,11 +96,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do        replies_count: 0,        favourites_count: 0,        reblogged: false, +      bookmarked: false,        favourited: false,        muted: false,        pinned: false,        sensitive: false, -      spoiler_text: note.data["object"]["summary"], +      spoiler_text: HtmlSanitizeEx.basic_html(note.data["object"]["summary"]),        visibility: "public",        media_attachments: [],        mentions: [], @@ -117,12 +123,34 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do            static_url: "corndog.png",            visible_in_picker: false          } -      ] +      ], +      pleroma: %{ +        local: true, +        conversation_id: convo_id, +        content: %{"text/plain" => HtmlSanitizeEx.strip_tags(note.data["object"]["content"])}, +        spoiler_text: %{"text/plain" => HtmlSanitizeEx.strip_tags(note.data["object"]["summary"])} +      }      }      assert status == expected    end +  test "tells if the message is muted for some reason" do +    user = insert(:user) +    other_user = insert(:user) + +    {:ok, user} = User.mute(user, other_user) + +    {:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"}) +    status = StatusView.render("status.json", %{activity: activity}) + +    assert status.muted == false + +    status = StatusView.render("status.json", %{activity: activity, for: user}) + +    assert status.muted == true +  end +    test "a reply" do      note = insert(:note_activity)      user = insert(:user) @@ -149,7 +177,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      status = StatusView.render("status.json", %{activity: activity}) -    actor = Repo.get_by(User, ap_id: activity.actor) +    actor = User.get_by_ap_id(activity.actor)      assert status.mentions ==               Enum.map([user, actor], fn u -> AccountView.render("mention.json", %{user: u}) end) @@ -174,7 +202,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do        remote_url: "someurl",        preview_url: "someurl",        text_url: "someurl", -      description: nil +      description: nil, +      pleroma: %{mime_type: "image/png"}      }      assert expected == StatusView.render("attachment.json", %{attachment: object}) @@ -233,4 +262,59 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do               ]      end    end + +  describe "rich media cards" do +    test "a rich media card without a site name renders correctly" do +      page_url = "http://example.com" + +      card = %{ +        url: page_url, +        image: page_url <> "/example.jpg", +        title: "Example website" +      } + +      %{provider_name: "example.com"} = +        StatusView.render("card.json", %{page_url: page_url, rich_media: card}) +    end + +    test "a rich media card without a site name or image renders correctly" do +      page_url = "http://example.com" + +      card = %{ +        url: page_url, +        title: "Example website" +      } + +      %{provider_name: "example.com"} = +        StatusView.render("card.json", %{page_url: page_url, rich_media: card}) +    end + +    test "a rich media card without an image renders correctly" do +      page_url = "http://example.com" + +      card = %{ +        url: page_url, +        site_name: "Example site name", +        title: "Example website" +      } + +      %{provider_name: "Example site name"} = +        StatusView.render("card.json", %{page_url: page_url, rich_media: card}) +    end + +    test "a rich media card with all relevant data renders correctly" do +      page_url = "http://example.com" + +      card = %{ +        url: page_url, +        site_name: "Example site name", +        title: "Example website", +        image: page_url <> "/example.jpg", +        description: "Example description" +      } + +      %{provider_name: "Example site name"} = +        StatusView.render("card.json", %{page_url: page_url, rich_media: card}) +    end +  end  end diff --git a/test/web/mastodon_api/subscription_controller_test.exs b/test/web/mastodon_api/subscription_controller_test.exs new file mode 100644 index 000000000..7dfb02f63 --- /dev/null +++ b/test/web/mastodon_api/subscription_controller_test.exs @@ -0,0 +1,192 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do +  use Pleroma.Web.ConnCase + +  import Pleroma.Factory +  alias Pleroma.Web.Push +  alias Pleroma.Web.Push.Subscription + +  @sub %{ +    "endpoint" => "https://example.com/example/1234", +    "keys" => %{ +      "auth" => "8eDyX_uCN0XRhSbY5hs7Hg==", +      "p256dh" => +        "BCIWgsnyXDv1VkhqL2P7YRBvdeuDnlwAPT2guNhdIoW3IP7GmHh1SMKPLxRf7x8vJy6ZFK3ol2ohgn_-0yP7QQA=" +    } +  } +  @server_key Keyword.get(Push.vapid_config(), :public_key) + +  setup do +    user = insert(:user) +    token = insert(:oauth_token, user: user, scopes: ["push"]) + +    conn = +      build_conn() +      |> assign(:user, user) +      |> assign(:token, token) + +    %{conn: conn, user: user, token: token} +  end + +  defmacro assert_error_when_disable_push(do: yield) do +    quote do +      vapid_details = Application.get_env(:web_push_encryption, :vapid_details, []) +      Application.put_env(:web_push_encryption, :vapid_details, []) +      assert "Something went wrong" == unquote(yield) +      Application.put_env(:web_push_encryption, :vapid_details, vapid_details) +    end +  end + +  describe "creates push subscription" do +    test "returns error when push disabled ", %{conn: conn} do +      assert_error_when_disable_push do +        conn +        |> post("/api/v1/push/subscription", %{}) +        |> json_response(500) +      end +    end + +    test "successful creation", %{conn: conn} do +      result = +        conn +        |> post("/api/v1/push/subscription", %{ +          "data" => %{"alerts" => %{"mention" => true, "test" => true}}, +          "subscription" => @sub +        }) +        |> json_response(200) + +      [subscription] = Pleroma.Repo.all(Subscription) + +      assert %{ +               "alerts" => %{"mention" => true}, +               "endpoint" => subscription.endpoint, +               "id" => to_string(subscription.id), +               "server_key" => @server_key +             } == result +    end +  end + +  describe "gets a user subscription" do +    test "returns error when push disabled ", %{conn: conn} do +      assert_error_when_disable_push do +        conn +        |> get("/api/v1/push/subscription", %{}) +        |> json_response(500) +      end +    end + +    test "returns error when user hasn't subscription", %{conn: conn} do +      res = +        conn +        |> get("/api/v1/push/subscription", %{}) +        |> json_response(404) + +      assert "Not found" == res +    end + +    test "returns a user subsciption", %{conn: conn, user: user, token: token} do +      subscription = +        insert(:push_subscription, +          user: user, +          token: token, +          data: %{"alerts" => %{"mention" => true}} +        ) + +      res = +        conn +        |> get("/api/v1/push/subscription", %{}) +        |> json_response(200) + +      expect = %{ +        "alerts" => %{"mention" => true}, +        "endpoint" => "https://example.com/example/1234", +        "id" => to_string(subscription.id), +        "server_key" => @server_key +      } + +      assert expect == res +    end +  end + +  describe "updates a user subsciption" do +    setup %{conn: conn, user: user, token: token} do +      subscription = +        insert(:push_subscription, +          user: user, +          token: token, +          data: %{"alerts" => %{"mention" => true}} +        ) + +      %{conn: conn, user: user, token: token, subscription: subscription} +    end + +    test "returns error when push disabled ", %{conn: conn} do +      assert_error_when_disable_push do +        conn +        |> put("/api/v1/push/subscription", %{data: %{"alerts" => %{"mention" => false}}}) +        |> json_response(500) +      end +    end + +    test "returns updated subsciption", %{conn: conn, subscription: subscription} do +      res = +        conn +        |> put("/api/v1/push/subscription", %{ +          data: %{"alerts" => %{"mention" => false, "follow" => true}} +        }) +        |> json_response(200) + +      expect = %{ +        "alerts" => %{"follow" => true, "mention" => false}, +        "endpoint" => "https://example.com/example/1234", +        "id" => to_string(subscription.id), +        "server_key" => @server_key +      } + +      assert expect == res +    end +  end + +  describe "deletes the user subscription" do +    test "returns error when push disabled ", %{conn: conn} do +      assert_error_when_disable_push do +        conn +        |> delete("/api/v1/push/subscription", %{}) +        |> json_response(500) +      end +    end + +    test "returns error when user hasn't subscription", %{conn: conn} do +      res = +        conn +        |> delete("/api/v1/push/subscription", %{}) +        |> json_response(404) + +      assert "Not found" == res +    end + +    test "returns empty result and delete user subsciption", %{ +      conn: conn, +      user: user, +      token: token +    } do +      subscription = +        insert(:push_subscription, +          user: user, +          token: token, +          data: %{"alerts" => %{"mention" => true}} +        ) + +      res = +        conn +        |> delete("/api/v1/push/subscription", %{}) +        |> json_response(200) + +      assert %{} == res +      refute Pleroma.Repo.get(Subscription, subscription.id) +    end +  end +end diff --git a/test/web/node_info_test.exs b/test/web/node_info_test.exs index 5981c70a7..2fc42b7cc 100644 --- a/test/web/node_info_test.exs +++ b/test/web/node_info_test.exs @@ -8,21 +8,23 @@ defmodule Pleroma.Web.NodeInfoTest do    import Pleroma.Factory    test "nodeinfo shows staff accounts", %{conn: conn} do -    user = insert(:user, %{local: true, info: %{is_moderator: true}}) +    moderator = insert(:user, %{local: true, info: %{is_moderator: true}}) +    admin = insert(:user, %{local: true, info: %{is_admin: true}})      conn =        conn -      |> get("/nodeinfo/2.0.json") +      |> get("/nodeinfo/2.1.json")      assert result = json_response(conn, 200) -    assert user.ap_id in result["metadata"]["staffAccounts"] +    assert moderator.ap_id in result["metadata"]["staffAccounts"] +    assert admin.ap_id in result["metadata"]["staffAccounts"]    end    test "nodeinfo shows restricted nicknames", %{conn: conn} do      conn =        conn -      |> get("/nodeinfo/2.0.json") +      |> get("/nodeinfo/2.1.json")      assert result = json_response(conn, 200) @@ -42,7 +44,7 @@ defmodule Pleroma.Web.NodeInfoTest do      |> json_response(404)      conn -    |> get("/nodeinfo/2.0.json") +    |> get("/nodeinfo/2.1.json")      |> json_response(404)      instance = @@ -58,7 +60,75 @@ defmodule Pleroma.Web.NodeInfoTest do      |> json_response(200)      conn +    |> get("/nodeinfo/2.1.json") +    |> json_response(200) +  end + +  test "returns 404 when federation is disabled (nodeinfo 2.0)", %{conn: conn} do +    instance = +      Application.get_env(:pleroma, :instance) +      |> Keyword.put(:federating, false) + +    Application.put_env(:pleroma, :instance, instance) + +    conn +    |> get("/.well-known/nodeinfo") +    |> json_response(404) + +    conn +    |> get("/nodeinfo/2.0.json") +    |> json_response(404) + +    instance = +      Application.get_env(:pleroma, :instance) +      |> Keyword.put(:federating, true) + +    Application.put_env(:pleroma, :instance, instance) +  end + +  test "returns 200 when federation is enabled (nodeinfo 2.0)", %{conn: conn} do +    conn +    |> get("/.well-known/nodeinfo") +    |> json_response(200) + +    conn      |> get("/nodeinfo/2.0.json")      |> json_response(200)    end + +  test "returns software.repository field in nodeinfo 2.1", %{conn: conn} do +    conn +    |> get("/.well-known/nodeinfo") +    |> json_response(200) + +    conn = +      conn +      |> get("/nodeinfo/2.1.json") + +    assert result = json_response(conn, 200) +    assert Pleroma.Application.repository() == result["software"]["repository"] +  end + +  test "it returns the safe_dm_mentions feature if enabled", %{conn: conn} do +    option = Pleroma.Config.get([:instance, :safe_dm_mentions]) +    Pleroma.Config.put([:instance, :safe_dm_mentions], true) + +    response = +      conn +      |> get("/nodeinfo/2.1.json") +      |> json_response(:ok) + +    assert "safe_dm_mentions" in response["metadata"]["features"] + +    Pleroma.Config.put([:instance, :safe_dm_mentions], false) + +    response = +      conn +      |> get("/nodeinfo/2.1.json") +      |> json_response(:ok) + +    refute "safe_dm_mentions" in response["metadata"]["features"] + +    Pleroma.Config.put([:instance, :safe_dm_mentions], option) +  end  end diff --git a/test/web/oauth/authorization_test.exs b/test/web/oauth/authorization_test.exs index 3b1ddada8..d8b008437 100644 --- a/test/web/oauth/authorization_test.exs +++ b/test/web/oauth/authorization_test.exs @@ -4,39 +4,41 @@  defmodule Pleroma.Web.OAuth.AuthorizationTest do    use Pleroma.DataCase -  alias Pleroma.Web.OAuth.{Authorization, App} +  alias Pleroma.Web.OAuth.App +  alias Pleroma.Web.OAuth.Authorization    import Pleroma.Factory -  test "create an authorization token for a valid app" do +  setup do      {:ok, app} =        Repo.insert(          App.register_changeset(%App{}, %{            client_name: "client", -          scopes: "scope", +          scopes: ["read", "write"],            redirect_uris: "url"          })        ) +    %{app: app} +  end + +  test "create an authorization token for a valid app", %{app: app} do      user = insert(:user) -    {:ok, auth} = Authorization.create_authorization(app, user) +    {:ok, auth1} = Authorization.create_authorization(app, user) +    assert auth1.scopes == app.scopes -    assert auth.user_id == user.id -    assert auth.app_id == app.id -    assert String.length(auth.token) > 10 -    assert auth.used == false -  end +    {:ok, auth2} = Authorization.create_authorization(app, user, ["read"]) +    assert auth2.scopes == ["read"] -  test "use up a token" do -    {:ok, app} = -      Repo.insert( -        App.register_changeset(%App{}, %{ -          client_name: "client", -          scopes: "scope", -          redirect_uris: "url" -        }) -      ) +    for auth <- [auth1, auth2] do +      assert auth.user_id == user.id +      assert auth.app_id == app.id +      assert String.length(auth.token) > 10 +      assert auth.used == false +    end +  end +  test "use up a token", %{app: app} do      user = insert(:user)      {:ok, auth} = Authorization.create_authorization(app, user) @@ -60,16 +62,7 @@ defmodule Pleroma.Web.OAuth.AuthorizationTest do      assert {:error, "token expired"} == Authorization.use_token(expired_auth)    end -  test "delete authorizations" do -    {:ok, app} = -      Repo.insert( -        App.register_changeset(%App{}, %{ -          client_name: "client", -          scopes: "scope", -          redirect_uris: "url" -        }) -      ) - +  test "delete authorizations", %{app: app} do      user = insert(:user)      {:ok, auth} = Authorization.create_authorization(app, user) diff --git a/test/web/oauth/ldap_authorization_test.exs b/test/web/oauth/ldap_authorization_test.exs new file mode 100644 index 000000000..0eb191c76 --- /dev/null +++ b/test/web/oauth/ldap_authorization_test.exs @@ -0,0 +1,195 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.OAuth.LDAPAuthorizationTest do +  use Pleroma.Web.ConnCase +  alias Pleroma.Repo +  alias Pleroma.Web.OAuth.Token +  import Pleroma.Factory +  import ExUnit.CaptureLog +  import Mock + +  @skip if !Code.ensure_loaded?(:eldap), do: :skip + +  setup_all do +    ldap_authenticator = +      Pleroma.Config.get(Pleroma.Web.Auth.Authenticator, Pleroma.Web.Auth.PleromaAuthenticator) + +    ldap_enabled = Pleroma.Config.get([:ldap, :enabled]) + +    on_exit(fn -> +      Pleroma.Config.put(Pleroma.Web.Auth.Authenticator, ldap_authenticator) +      Pleroma.Config.put([:ldap, :enabled], ldap_enabled) +    end) + +    Pleroma.Config.put(Pleroma.Web.Auth.Authenticator, Pleroma.Web.Auth.LDAPAuthenticator) +    Pleroma.Config.put([:ldap, :enabled], true) + +    :ok +  end + +  @tag @skip +  test "authorizes the existing user using LDAP credentials" do +    password = "testpassword" +    user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password)) +    app = insert(:oauth_app, scopes: ["read", "write"]) + +    host = Pleroma.Config.get([:ldap, :host]) |> to_charlist +    port = Pleroma.Config.get([:ldap, :port]) + +    with_mocks [ +      {:eldap, [], +       [ +         open: fn [^host], [{:port, ^port}, {:ssl, false} | _] -> {:ok, self()} end, +         simple_bind: fn _connection, _dn, ^password -> :ok end, +         close: fn _connection -> +           send(self(), :close_connection) +           :ok +         end +       ]} +    ] do +      conn = +        build_conn() +        |> post("/oauth/token", %{ +          "grant_type" => "password", +          "username" => user.nickname, +          "password" => password, +          "client_id" => app.client_id, +          "client_secret" => app.client_secret +        }) + +      assert %{"access_token" => token} = json_response(conn, 200) + +      token = Repo.get_by(Token, token: token) + +      assert token.user_id == user.id +      assert_received :close_connection +    end +  end + +  @tag @skip +  test "creates a new user after successful LDAP authorization" do +    password = "testpassword" +    user = build(:user) +    app = insert(:oauth_app, scopes: ["read", "write"]) + +    host = Pleroma.Config.get([:ldap, :host]) |> to_charlist +    port = Pleroma.Config.get([:ldap, :port]) + +    with_mocks [ +      {:eldap, [], +       [ +         open: fn [^host], [{:port, ^port}, {:ssl, false} | _] -> {:ok, self()} end, +         simple_bind: fn _connection, _dn, ^password -> :ok end, +         equalityMatch: fn _type, _value -> :ok end, +         wholeSubtree: fn -> :ok end, +         search: fn _connection, _options -> +           {:ok, +            {:eldap_search_result, [{:eldap_entry, '', [{'mail', [to_charlist(user.email)]}]}], +             []}} +         end, +         close: fn _connection -> +           send(self(), :close_connection) +           :ok +         end +       ]} +    ] do +      conn = +        build_conn() +        |> post("/oauth/token", %{ +          "grant_type" => "password", +          "username" => user.nickname, +          "password" => password, +          "client_id" => app.client_id, +          "client_secret" => app.client_secret +        }) + +      assert %{"access_token" => token} = json_response(conn, 200) + +      token = Repo.get_by(Token, token: token) |> Repo.preload(:user) + +      assert token.user.nickname == user.nickname +      assert_received :close_connection +    end +  end + +  @tag @skip +  test "falls back to the default authorization when LDAP is unavailable" do +    password = "testpassword" +    user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password)) +    app = insert(:oauth_app, scopes: ["read", "write"]) + +    host = Pleroma.Config.get([:ldap, :host]) |> to_charlist +    port = Pleroma.Config.get([:ldap, :port]) + +    with_mocks [ +      {:eldap, [], +       [ +         open: fn [^host], [{:port, ^port}, {:ssl, false} | _] -> {:error, 'connect failed'} end, +         simple_bind: fn _connection, _dn, ^password -> :ok end, +         close: fn _connection -> +           send(self(), :close_connection) +           :ok +         end +       ]} +    ] do +      log = +        capture_log(fn -> +          conn = +            build_conn() +            |> post("/oauth/token", %{ +              "grant_type" => "password", +              "username" => user.nickname, +              "password" => password, +              "client_id" => app.client_id, +              "client_secret" => app.client_secret +            }) + +          assert %{"access_token" => token} = json_response(conn, 200) + +          token = Repo.get_by(Token, token: token) + +          assert token.user_id == user.id +        end) + +      assert log =~ "Could not open LDAP connection: 'connect failed'" +      refute_received :close_connection +    end +  end + +  @tag @skip +  test "disallow authorization for wrong LDAP credentials" do +    password = "testpassword" +    user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password)) +    app = insert(:oauth_app, scopes: ["read", "write"]) + +    host = Pleroma.Config.get([:ldap, :host]) |> to_charlist +    port = Pleroma.Config.get([:ldap, :port]) + +    with_mocks [ +      {:eldap, [], +       [ +         open: fn [^host], [{:port, ^port}, {:ssl, false} | _] -> {:ok, self()} end, +         simple_bind: fn _connection, _dn, ^password -> {:error, :invalidCredentials} end, +         close: fn _connection -> +           send(self(), :close_connection) +           :ok +         end +       ]} +    ] do +      conn = +        build_conn() +        |> post("/oauth/token", %{ +          "grant_type" => "password", +          "username" => user.nickname, +          "password" => password, +          "client_id" => app.client_id, +          "client_secret" => app.client_secret +        }) + +      assert %{"error" => "Invalid credentials"} = json_response(conn, 400) +      assert_received :close_connection +    end +  end +end diff --git a/test/web/oauth/oauth_controller_test.exs b/test/web/oauth/oauth_controller_test.exs index ccd552258..ac7843f9b 100644 --- a/test/web/oauth/oauth_controller_test.exs +++ b/test/web/oauth/oauth_controller_test.exs @@ -5,170 +5,676 @@  defmodule Pleroma.Web.OAuth.OAuthControllerTest do    use Pleroma.Web.ConnCase    import Pleroma.Factory +  import Mock +  alias Pleroma.Registration    alias Pleroma.Repo -  alias Pleroma.Web.OAuth.{Authorization, Token} - -  test "redirects with oauth authorization" do -    user = insert(:user) -    app = insert(:oauth_app) - -    conn = -      build_conn() -      |> post("/oauth/authorize", %{ -        "authorization" => %{ -          "name" => user.nickname, -          "password" => "test", -          "client_id" => app.client_id, -          "redirect_uri" => app.redirect_uris, -          "state" => "statepassed" -        } -      }) +  alias Pleroma.Web.OAuth.Authorization +  alias Pleroma.Web.OAuth.Token + +  @session_opts [ +    store: :cookie, +    key: "_test", +    signing_salt: "cooldude" +  ] + +  describe "in OAuth consumer mode, " do +    setup do +      oauth_consumer_strategies_path = [:auth, :oauth_consumer_strategies] +      oauth_consumer_strategies = Pleroma.Config.get(oauth_consumer_strategies_path) +      Pleroma.Config.put(oauth_consumer_strategies_path, ~w(twitter facebook)) + +      on_exit(fn -> +        Pleroma.Config.put(oauth_consumer_strategies_path, oauth_consumer_strategies) +      end) + +      [ +        app: insert(:oauth_app), +        conn: +          build_conn() +          |> Plug.Session.call(Plug.Session.init(@session_opts)) +          |> fetch_session() +      ] +    end -    target = redirected_to(conn) -    assert target =~ app.redirect_uris +    test "GET /oauth/authorize renders auth forms, including OAuth consumer form", %{ +      app: app, +      conn: conn +    } do +      conn = +        get( +          conn, +          "/oauth/authorize", +          %{ +            "response_type" => "code", +            "client_id" => app.client_id, +            "redirect_uri" => app.redirect_uris, +            "scope" => "read" +          } +        ) + +      assert response = html_response(conn, 200) +      assert response =~ "Sign in with Twitter" +      assert response =~ o_auth_path(conn, :prepare_request) +    end -    query = URI.parse(target).query |> URI.query_decoder() |> Map.new() +    test "GET /oauth/prepare_request encodes parameters as `state` and redirects", %{ +      app: app, +      conn: conn +    } do +      conn = +        get( +          conn, +          "/oauth/prepare_request", +          %{ +            "provider" => "twitter", +            "scope" => "read follow", +            "client_id" => app.client_id, +            "redirect_uri" => app.redirect_uris, +            "state" => "a_state" +          } +        ) + +      assert response = html_response(conn, 302) + +      redirect_query = URI.parse(redirected_to(conn)).query +      assert %{"state" => state_param} = URI.decode_query(redirect_query) +      assert {:ok, state_components} = Poison.decode(state_param) + +      expected_client_id = app.client_id +      expected_redirect_uri = app.redirect_uris + +      assert %{ +               "scope" => "read follow", +               "client_id" => ^expected_client_id, +               "redirect_uri" => ^expected_redirect_uri, +               "state" => "a_state" +             } = state_components +    end -    assert %{"state" => "statepassed", "code" => code} = query -    assert Repo.get_by(Authorization, token: code) -  end +    test "with user-bound registration, GET /oauth/<provider>/callback redirects to `redirect_uri` with `code`", +         %{app: app, conn: conn} do +      registration = insert(:registration) -  test "issues a token for an all-body request" do -    user = insert(:user) -    app = insert(:oauth_app) +      state_params = %{ +        "scope" => Enum.join(app.scopes, " "), +        "client_id" => app.client_id, +        "redirect_uri" => app.redirect_uris, +        "state" => "" +      } + +      with_mock Pleroma.Web.Auth.Authenticator, +        get_registration: fn _, _ -> {:ok, registration} end do +        conn = +          get( +            conn, +            "/oauth/twitter/callback", +            %{ +              "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM", +              "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs", +              "provider" => "twitter", +              "state" => Poison.encode!(state_params) +            } +          ) + +        assert response = html_response(conn, 302) +        assert redirected_to(conn) =~ ~r/#{app.redirect_uris}\?code=.+/ +      end +    end -    {:ok, auth} = Authorization.create_authorization(app, user) +    test "with user-unbound registration, GET /oauth/<provider>/callback renders registration_details page", +         %{app: app, conn: conn} do +      registration = insert(:registration, user: nil) -    conn = -      build_conn() -      |> post("/oauth/token", %{ -        "grant_type" => "authorization_code", -        "code" => auth.token, -        "redirect_uri" => app.redirect_uris, +      state_params = %{ +        "scope" => "read write",          "client_id" => app.client_id, -        "client_secret" => app.client_secret -      }) +        "redirect_uri" => app.redirect_uris, +        "state" => "a_state" +      } + +      with_mock Pleroma.Web.Auth.Authenticator, +        get_registration: fn _, _ -> {:ok, registration} end do +        conn = +          get( +            conn, +            "/oauth/twitter/callback", +            %{ +              "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM", +              "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs", +              "provider" => "twitter", +              "state" => Poison.encode!(state_params) +            } +          ) + +        assert response = html_response(conn, 200) +        assert response =~ ~r/name="op" type="submit" value="register"/ +        assert response =~ ~r/name="op" type="submit" value="connect"/ +        assert response =~ Registration.email(registration) +        assert response =~ Registration.nickname(registration) +      end +    end -    assert %{"access_token" => token} = json_response(conn, 200) -    assert Repo.get_by(Token, token: token) -  end +    test "on authentication error, GET /oauth/<provider>/callback redirects to `redirect_uri`", %{ +      app: app, +      conn: conn +    } do +      state_params = %{ +        "scope" => Enum.join(app.scopes, " "), +        "client_id" => app.client_id, +        "redirect_uri" => app.redirect_uris, +        "state" => "" +      } + +      conn = +        conn +        |> assign(:ueberauth_failure, %{errors: [%{message: "(error description)"}]}) +        |> get( +          "/oauth/twitter/callback", +          %{ +            "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM", +            "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs", +            "provider" => "twitter", +            "state" => Poison.encode!(state_params) +          } +        ) + +      assert response = html_response(conn, 302) +      assert redirected_to(conn) == app.redirect_uris +      assert get_flash(conn, :error) == "Failed to authenticate: (error description)." +    end -  test "issues a token for `password` grant_type with valid credentials" do -    password = "testpassword" -    user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password)) +    test "GET /oauth/registration_details renders registration details form", %{ +      app: app, +      conn: conn +    } do +      conn = +        get( +          conn, +          "/oauth/registration_details", +          %{ +            "scopes" => app.scopes, +            "client_id" => app.client_id, +            "redirect_uri" => app.redirect_uris, +            "state" => "a_state", +            "nickname" => nil, +            "email" => "john@doe.com" +          } +        ) + +      assert response = html_response(conn, 200) +      assert response =~ ~r/name="op" type="submit" value="register"/ +      assert response =~ ~r/name="op" type="submit" value="connect"/ +    end -    app = insert(:oauth_app) +    test "with valid params, POST /oauth/register?op=register redirects to `redirect_uri` with `code`", +         %{ +           app: app, +           conn: conn +         } do +      registration = insert(:registration, user: nil, info: %{"nickname" => nil, "email" => nil}) + +      conn = +        conn +        |> put_session(:registration_id, registration.id) +        |> post( +          "/oauth/register", +          %{ +            "op" => "register", +            "scopes" => app.scopes, +            "client_id" => app.client_id, +            "redirect_uri" => app.redirect_uris, +            "state" => "a_state", +            "nickname" => "availablenick", +            "email" => "available@email.com" +          } +        ) + +      assert response = html_response(conn, 302) +      assert redirected_to(conn) =~ ~r/#{app.redirect_uris}\?code=.+/ +    end -    conn = -      build_conn() -      |> post("/oauth/token", %{ -        "grant_type" => "password", -        "username" => user.nickname, -        "password" => password, +    test "with invalid params, POST /oauth/register?op=register renders registration_details page", +         %{ +           app: app, +           conn: conn +         } do +      another_user = insert(:user) +      registration = insert(:registration, user: nil, info: %{"nickname" => nil, "email" => nil}) + +      params = %{ +        "op" => "register", +        "scopes" => app.scopes,          "client_id" => app.client_id, -        "client_secret" => app.client_secret -      }) +        "redirect_uri" => app.redirect_uris, +        "state" => "a_state", +        "nickname" => "availablenickname", +        "email" => "available@email.com" +      } + +      for {bad_param, bad_param_value} <- +            [{"nickname", another_user.nickname}, {"email", another_user.email}] do +        bad_params = Map.put(params, bad_param, bad_param_value) + +        conn = +          conn +          |> put_session(:registration_id, registration.id) +          |> post("/oauth/register", bad_params) + +        assert html_response(conn, 403) =~ ~r/name="op" type="submit" value="register"/ +        assert get_flash(conn, :error) == "Error: #{bad_param} has already been taken." +      end +    end -    assert %{"access_token" => token} = json_response(conn, 200) -    assert Repo.get_by(Token, token: token) -  end +    test "with valid params, POST /oauth/register?op=connect redirects to `redirect_uri` with `code`", +         %{ +           app: app, +           conn: conn +         } do +      user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt("testpassword")) +      registration = insert(:registration, user: nil) + +      conn = +        conn +        |> put_session(:registration_id, registration.id) +        |> post( +          "/oauth/register", +          %{ +            "op" => "connect", +            "scopes" => app.scopes, +            "client_id" => app.client_id, +            "redirect_uri" => app.redirect_uris, +            "state" => "a_state", +            "auth_name" => user.nickname, +            "password" => "testpassword" +          } +        ) + +      assert response = html_response(conn, 302) +      assert redirected_to(conn) =~ ~r/#{app.redirect_uris}\?code=.+/ +    end -  test "issues a token for request with HTTP basic auth client credentials" do -    user = insert(:user) -    app = insert(:oauth_app) +    test "with invalid params, POST /oauth/register?op=connect renders registration_details page", +         %{ +           app: app, +           conn: conn +         } do +      user = insert(:user) +      registration = insert(:registration, user: nil) + +      params = %{ +        "op" => "connect", +        "scopes" => app.scopes, +        "client_id" => app.client_id, +        "redirect_uri" => app.redirect_uris, +        "state" => "a_state", +        "auth_name" => user.nickname, +        "password" => "wrong password" +      } + +      conn = +        conn +        |> put_session(:registration_id, registration.id) +        |> post("/oauth/register", params) + +      assert html_response(conn, 401) =~ ~r/name="op" type="submit" value="connect"/ +      assert get_flash(conn, :error) == "Invalid Username/Password" +    end +  end -    {:ok, auth} = Authorization.create_authorization(app, user) +  describe "GET /oauth/authorize" do +    setup do +      [ +        app: insert(:oauth_app, redirect_uris: "https://redirect.url"), +        conn: +          build_conn() +          |> Plug.Session.call(Plug.Session.init(@session_opts)) +          |> fetch_session() +      ] +    end -    app_encoded = -      (URI.encode_www_form(app.client_id) <> ":" <> URI.encode_www_form(app.client_secret)) -      |> Base.encode64() +    test "renders authentication page", %{app: app, conn: conn} do +      conn = +        get( +          conn, +          "/oauth/authorize", +          %{ +            "response_type" => "code", +            "client_id" => app.client_id, +            "redirect_uri" => app.redirect_uris, +            "scope" => "read" +          } +        ) + +      assert html_response(conn, 200) =~ ~s(type="submit") +    end -    conn = -      build_conn() -      |> put_req_header("authorization", "Basic " <> app_encoded) -      |> post("/oauth/token", %{ -        "grant_type" => "authorization_code", -        "code" => auth.token, -        "redirect_uri" => app.redirect_uris -      }) +    test "renders authentication page if user is already authenticated but `force_login` is tru-ish", +         %{app: app, conn: conn} do +      token = insert(:oauth_token, app_id: app.id) + +      conn = +        conn +        |> put_session(:oauth_token, token.token) +        |> get( +          "/oauth/authorize", +          %{ +            "response_type" => "code", +            "client_id" => app.client_id, +            "redirect_uri" => app.redirect_uris, +            "scope" => "read", +            "force_login" => "true" +          } +        ) + +      assert html_response(conn, 200) =~ ~s(type="submit") +    end -    assert %{"access_token" => token} = json_response(conn, 200) -    assert Repo.get_by(Token, token: token) +    test "redirects to app if user is already authenticated", %{app: app, conn: conn} do +      token = insert(:oauth_token, app_id: app.id) + +      conn = +        conn +        |> put_session(:oauth_token, token.token) +        |> get( +          "/oauth/authorize", +          %{ +            "response_type" => "code", +            "client_id" => app.client_id, +            "redirect_uri" => app.redirect_uris, +            "scope" => "read" +          } +        ) + +      assert redirected_to(conn) == "https://redirect.url" +    end    end -  test "rejects token exchange with invalid client credentials" do -    user = insert(:user) -    app = insert(:oauth_app) +  describe "POST /oauth/authorize" do +    test "redirects with oauth authorization" do +      user = insert(:user) +      app = insert(:oauth_app, scopes: ["read", "write", "follow"]) + +      conn = +        build_conn() +        |> post("/oauth/authorize", %{ +          "authorization" => %{ +            "name" => user.nickname, +            "password" => "test", +            "client_id" => app.client_id, +            "redirect_uri" => app.redirect_uris, +            "scope" => "read write", +            "state" => "statepassed" +          } +        }) + +      target = redirected_to(conn) +      assert target =~ app.redirect_uris + +      query = URI.parse(target).query |> URI.query_decoder() |> Map.new() + +      assert %{"state" => "statepassed", "code" => code} = query +      auth = Repo.get_by(Authorization, token: code) +      assert auth +      assert auth.scopes == ["read", "write"] +    end -    {:ok, auth} = Authorization.create_authorization(app, user) +    test "returns 401 for wrong credentials", %{conn: conn} do +      user = insert(:user) +      app = insert(:oauth_app) + +      result = +        conn +        |> post("/oauth/authorize", %{ +          "authorization" => %{ +            "name" => user.nickname, +            "password" => "wrong", +            "client_id" => app.client_id, +            "redirect_uri" => app.redirect_uris, +            "state" => "statepassed", +            "scope" => Enum.join(app.scopes, " ") +          } +        }) +        |> html_response(:unauthorized) + +      # Keep the details +      assert result =~ app.client_id +      assert result =~ app.redirect_uris + +      # Error message +      assert result =~ "Invalid Username/Password" +    end -    conn = -      build_conn() -      |> put_req_header("authorization", "Basic JTIxOiVGMCU5RiVBNCVCNwo=") -      |> post("/oauth/token", %{ -        "grant_type" => "authorization_code", -        "code" => auth.token, -        "redirect_uri" => app.redirect_uris -      }) +    test "returns 401 for missing scopes", %{conn: conn} do +      user = insert(:user) +      app = insert(:oauth_app) + +      result = +        conn +        |> post("/oauth/authorize", %{ +          "authorization" => %{ +            "name" => user.nickname, +            "password" => "test", +            "client_id" => app.client_id, +            "redirect_uri" => app.redirect_uris, +            "state" => "statepassed", +            "scope" => "" +          } +        }) +        |> html_response(:unauthorized) + +      # Keep the details +      assert result =~ app.client_id +      assert result =~ app.redirect_uris + +      # Error message +      assert result =~ "This action is outside the authorized scopes" +    end -    assert resp = json_response(conn, 400) -    assert %{"error" => _} = resp -    refute Map.has_key?(resp, "access_token") +    test "returns 401 for scopes beyond app scopes", %{conn: conn} do +      user = insert(:user) +      app = insert(:oauth_app, scopes: ["read", "write"]) + +      result = +        conn +        |> post("/oauth/authorize", %{ +          "authorization" => %{ +            "name" => user.nickname, +            "password" => "test", +            "client_id" => app.client_id, +            "redirect_uri" => app.redirect_uris, +            "state" => "statepassed", +            "scope" => "read write follow" +          } +        }) +        |> html_response(:unauthorized) + +      # Keep the details +      assert result =~ app.client_id +      assert result =~ app.redirect_uris + +      # Error message +      assert result =~ "This action is outside the authorized scopes" +    end    end -  test "rejects token exchange for valid credentials belonging to unconfirmed user and confirmation is required" do -    setting = Pleroma.Config.get([:instance, :account_activation_required]) +  describe "POST /oauth/token" do +    test "issues a token for an all-body request" do +      user = insert(:user) +      app = insert(:oauth_app, scopes: ["read", "write"]) + +      {:ok, auth} = Authorization.create_authorization(app, user, ["write"]) + +      conn = +        build_conn() +        |> post("/oauth/token", %{ +          "grant_type" => "authorization_code", +          "code" => auth.token, +          "redirect_uri" => app.redirect_uris, +          "client_id" => app.client_id, +          "client_secret" => app.client_secret +        }) + +      assert %{"access_token" => token, "me" => ap_id} = json_response(conn, 200) -    unless setting do -      Pleroma.Config.put([:instance, :account_activation_required], true) -      on_exit(fn -> Pleroma.Config.put([:instance, :account_activation_required], setting) end) +      token = Repo.get_by(Token, token: token) +      assert token +      assert token.scopes == auth.scopes +      assert user.ap_id == ap_id      end -    password = "testpassword" -    user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password)) -    info_change = Pleroma.User.Info.confirmation_changeset(user.info, :unconfirmed) +    test "issues a token for `password` grant_type with valid credentials, with full permissions by default" do +      password = "testpassword" +      user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password)) -    {:ok, user} = -      user -      |> Ecto.Changeset.change() -      |> Ecto.Changeset.put_embed(:info, info_change) -      |> Repo.update() +      app = insert(:oauth_app, scopes: ["read", "write"]) -    refute Pleroma.User.auth_active?(user) +      # Note: "scope" param is intentionally omitted +      conn = +        build_conn() +        |> post("/oauth/token", %{ +          "grant_type" => "password", +          "username" => user.nickname, +          "password" => password, +          "client_id" => app.client_id, +          "client_secret" => app.client_secret +        }) -    app = insert(:oauth_app) +      assert %{"access_token" => token} = json_response(conn, 200) -    conn = -      build_conn() -      |> post("/oauth/token", %{ -        "grant_type" => "password", -        "username" => user.nickname, -        "password" => password, -        "client_id" => app.client_id, -        "client_secret" => app.client_secret -      }) +      token = Repo.get_by(Token, token: token) +      assert token +      assert token.scopes == app.scopes +    end -    assert resp = json_response(conn, 403) -    assert %{"error" => _} = resp -    refute Map.has_key?(resp, "access_token") -  end +    test "issues a token for request with HTTP basic auth client credentials" do +      user = insert(:user) +      app = insert(:oauth_app, scopes: ["scope1", "scope2", "scope3"]) -  test "rejects an invalid authorization code" do -    app = insert(:oauth_app) +      {:ok, auth} = Authorization.create_authorization(app, user, ["scope1", "scope2"]) +      assert auth.scopes == ["scope1", "scope2"] -    conn = -      build_conn() -      |> post("/oauth/token", %{ -        "grant_type" => "authorization_code", -        "code" => "Imobviouslyinvalid", -        "redirect_uri" => app.redirect_uris, -        "client_id" => app.client_id, -        "client_secret" => app.client_secret -      }) +      app_encoded = +        (URI.encode_www_form(app.client_id) <> ":" <> URI.encode_www_form(app.client_secret)) +        |> Base.encode64() + +      conn = +        build_conn() +        |> put_req_header("authorization", "Basic " <> app_encoded) +        |> post("/oauth/token", %{ +          "grant_type" => "authorization_code", +          "code" => auth.token, +          "redirect_uri" => app.redirect_uris +        }) + +      assert %{"access_token" => token, "scope" => scope} = json_response(conn, 200) + +      assert scope == "scope1 scope2" + +      token = Repo.get_by(Token, token: token) +      assert token +      assert token.scopes == ["scope1", "scope2"] +    end + +    test "rejects token exchange with invalid client credentials" do +      user = insert(:user) +      app = insert(:oauth_app) + +      {:ok, auth} = Authorization.create_authorization(app, user) + +      conn = +        build_conn() +        |> put_req_header("authorization", "Basic JTIxOiVGMCU5RiVBNCVCNwo=") +        |> post("/oauth/token", %{ +          "grant_type" => "authorization_code", +          "code" => auth.token, +          "redirect_uri" => app.redirect_uris +        }) + +      assert resp = json_response(conn, 400) +      assert %{"error" => _} = resp +      refute Map.has_key?(resp, "access_token") +    end + +    test "rejects token exchange for valid credentials belonging to unconfirmed user and confirmation is required" do +      setting = Pleroma.Config.get([:instance, :account_activation_required]) + +      unless setting do +        Pleroma.Config.put([:instance, :account_activation_required], true) +        on_exit(fn -> Pleroma.Config.put([:instance, :account_activation_required], setting) end) +      end + +      password = "testpassword" +      user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password)) +      info_change = Pleroma.User.Info.confirmation_changeset(user.info, :unconfirmed) -    assert resp = json_response(conn, 400) -    assert %{"error" => _} = json_response(conn, 400) -    refute Map.has_key?(resp, "access_token") +      {:ok, user} = +        user +        |> Ecto.Changeset.change() +        |> Ecto.Changeset.put_embed(:info, info_change) +        |> Repo.update() + +      refute Pleroma.User.auth_active?(user) + +      app = insert(:oauth_app) + +      conn = +        build_conn() +        |> post("/oauth/token", %{ +          "grant_type" => "password", +          "username" => user.nickname, +          "password" => password, +          "client_id" => app.client_id, +          "client_secret" => app.client_secret +        }) + +      assert resp = json_response(conn, 403) +      assert %{"error" => _} = resp +      refute Map.has_key?(resp, "access_token") +    end + +    test "rejects token exchange for valid credentials belonging to deactivated user" do +      password = "testpassword" + +      user = +        insert(:user, +          password_hash: Comeonin.Pbkdf2.hashpwsalt(password), +          info: %{deactivated: true} +        ) + +      app = insert(:oauth_app) + +      conn = +        build_conn() +        |> post("/oauth/token", %{ +          "grant_type" => "password", +          "username" => user.nickname, +          "password" => password, +          "client_id" => app.client_id, +          "client_secret" => app.client_secret +        }) + +      assert resp = json_response(conn, 403) +      assert %{"error" => _} = resp +      refute Map.has_key?(resp, "access_token") +    end + +    test "rejects an invalid authorization code" do +      app = insert(:oauth_app) + +      conn = +        build_conn() +        |> post("/oauth/token", %{ +          "grant_type" => "authorization_code", +          "code" => "Imobviouslyinvalid", +          "redirect_uri" => app.redirect_uris, +          "client_id" => app.client_id, +          "client_secret" => app.client_secret +        }) + +      assert resp = json_response(conn, 400) +      assert %{"error" => _} = json_response(conn, 400) +      refute Map.has_key?(resp, "access_token") +    end    end  end diff --git a/test/web/oauth/token_test.exs b/test/web/oauth/token_test.exs index 9a241d61a..ad2a49f09 100644 --- a/test/web/oauth/token_test.exs +++ b/test/web/oauth/token_test.exs @@ -4,29 +4,33 @@  defmodule Pleroma.Web.OAuth.TokenTest do    use Pleroma.DataCase -  alias Pleroma.Web.OAuth.{App, Token, Authorization}    alias Pleroma.Repo +  alias Pleroma.Web.OAuth.App +  alias Pleroma.Web.OAuth.Authorization +  alias Pleroma.Web.OAuth.Token    import Pleroma.Factory -  test "exchanges a auth token for an access token" do +  test "exchanges a auth token for an access token, preserving `scopes`" do      {:ok, app} =        Repo.insert(          App.register_changeset(%App{}, %{            client_name: "client", -          scopes: "scope", +          scopes: ["read", "write"],            redirect_uris: "url"          })        )      user = insert(:user) -    {:ok, auth} = Authorization.create_authorization(app, user) +    {:ok, auth} = Authorization.create_authorization(app, user, ["read"]) +    assert auth.scopes == ["read"]      {:ok, token} = Token.exchange_token(app, auth)      assert token.app_id == app.id      assert token.user_id == user.id +    assert token.scopes == auth.scopes      assert String.length(token.token) > 10      assert String.length(token.refresh_token) > 10 @@ -39,7 +43,7 @@ defmodule Pleroma.Web.OAuth.TokenTest do        Repo.insert(          App.register_changeset(%App{}, %{            client_name: "client1", -          scopes: "scope", +          scopes: ["scope"],            redirect_uris: "url"          })        ) @@ -48,7 +52,7 @@ defmodule Pleroma.Web.OAuth.TokenTest do        Repo.insert(          App.register_changeset(%App{}, %{            client_name: "client2", -          scopes: "scope", +          scopes: ["scope"],            redirect_uris: "url"          })        ) diff --git a/test/web/ostatus/activity_representer_test.exs b/test/web/ostatus/activity_representer_test.exs index 0869f2fd5..a4bb68c4d 100644 --- a/test/web/ostatus/activity_representer_test.exs +++ b/test/web/ostatus/activity_representer_test.exs @@ -5,10 +5,12 @@  defmodule Pleroma.Web.OStatus.ActivityRepresenterTest do    use Pleroma.DataCase -  alias Pleroma.Web.OStatus.ActivityRepresenter -  alias Pleroma.{User, Activity, Object} +  alias Pleroma.Activity +  alias Pleroma.Object +  alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.OStatus +  alias Pleroma.Web.OStatus.ActivityRepresenter    import Pleroma.Factory    import Tesla.Mock @@ -114,10 +116,10 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenterTest do      {:ok, announce, _object} = ActivityPub.announce(user, object) -    announce = Repo.get(Activity, announce.id) +    announce = Activity.get_by_id(announce.id)      note_user = User.get_cached_by_ap_id(note.data["actor"]) -    note = Repo.get(Activity, note.id) +    note = Activity.get_by_id(note.id)      note_xml =        ActivityRepresenter.to_simple_form(note, note_user, true) diff --git a/test/web/ostatus/feed_representer_test.exs b/test/web/ostatus/feed_representer_test.exs index 55717dec7..3c7b126e7 100644 --- a/test/web/ostatus/feed_representer_test.exs +++ b/test/web/ostatus/feed_representer_test.exs @@ -6,8 +6,10 @@ defmodule Pleroma.Web.OStatus.FeedRepresenterTest do    use Pleroma.DataCase    import Pleroma.Factory    alias Pleroma.User -  alias Pleroma.Web.OStatus.{FeedRepresenter, UserRepresenter, ActivityRepresenter}    alias Pleroma.Web.OStatus +  alias Pleroma.Web.OStatus.ActivityRepresenter +  alias Pleroma.Web.OStatus.FeedRepresenter +  alias Pleroma.Web.OStatus.UserRepresenter    test "returns a feed of the last 20 items of the user" do      note_activity = insert(:note_activity) diff --git a/test/web/ostatus/incoming_documents/delete_handling_test.exs b/test/web/ostatus/incoming_documents/delete_handling_test.exs index c8fbff6cc..ca6e61339 100644 --- a/test/web/ostatus/incoming_documents/delete_handling_test.exs +++ b/test/web/ostatus/incoming_documents/delete_handling_test.exs @@ -2,9 +2,17 @@ defmodule Pleroma.Web.OStatus.DeleteHandlingTest do    use Pleroma.DataCase    import Pleroma.Factory -  alias Pleroma.{Repo, Activity, Object} +  import Tesla.Mock + +  alias Pleroma.Activity +  alias Pleroma.Object    alias Pleroma.Web.OStatus +  setup do +    mock(fn env -> apply(HttpRequestMock, :request, [env]) end) +    :ok +  end +    describe "deletions" do      test "it removes the mentioned activity" do        note = insert(:note_activity) @@ -23,10 +31,10 @@ defmodule Pleroma.Web.OStatus.DeleteHandlingTest do        {:ok, [delete]} = OStatus.handle_incoming(incoming) -      refute Repo.get(Activity, note.id) -      refute Repo.get(Activity, like.id) +      refute Activity.get_by_id(note.id) +      refute Activity.get_by_id(like.id)        assert Object.get_by_ap_id(note.data["object"]["id"]).data["type"] == "Tombstone" -      assert Repo.get(Activity, second_note.id) +      assert Activity.get_by_id(second_note.id)        assert Object.get_by_ap_id(second_note.data["object"]["id"])        assert delete.data["type"] == "Delete" diff --git a/test/web/ostatus/ostatus_controller_test.exs b/test/web/ostatus/ostatus_controller_test.exs index 954abf5fe..2950f11c0 100644 --- a/test/web/ostatus/ostatus_controller_test.exs +++ b/test/web/ostatus/ostatus_controller_test.exs @@ -5,7 +5,9 @@  defmodule Pleroma.Web.OStatus.OStatusControllerTest do    use Pleroma.Web.ConnCase    import Pleroma.Factory -  alias Pleroma.{User, Repo, Object} +  alias Pleroma.Object +  alias Pleroma.Repo +  alias Pleroma.User    alias Pleroma.Web.CommonAPI    alias Pleroma.Web.OStatus.ActivityRepresenter @@ -14,49 +16,51 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do      :ok    end -  test "decodes a salmon", %{conn: conn} do -    user = insert(:user) -    salmon = File.read!("test/fixtures/salmon.xml") +  describe "salmon_incoming" do +    test "decodes a salmon", %{conn: conn} do +      user = insert(:user) +      salmon = File.read!("test/fixtures/salmon.xml") -    conn = -      conn -      |> put_req_header("content-type", "application/atom+xml") -      |> post("/users/#{user.nickname}/salmon", salmon) +      conn = +        conn +        |> put_req_header("content-type", "application/atom+xml") +        |> post("/users/#{user.nickname}/salmon", salmon) -    assert response(conn, 200) -  end +      assert response(conn, 200) +    end -  test "decodes a salmon with a changed magic key", %{conn: conn} do -    user = insert(:user) -    salmon = File.read!("test/fixtures/salmon.xml") +    test "decodes a salmon with a changed magic key", %{conn: conn} do +      user = insert(:user) +      salmon = File.read!("test/fixtures/salmon.xml") -    conn = -      conn -      |> put_req_header("content-type", "application/atom+xml") -      |> post("/users/#{user.nickname}/salmon", salmon) +      conn = +        conn +        |> put_req_header("content-type", "application/atom+xml") +        |> post("/users/#{user.nickname}/salmon", salmon) -    assert response(conn, 200) +      assert response(conn, 200) -    # Set a wrong magic-key for a user so it has to refetch -    salmon_user = User.get_by_ap_id("http://gs.example.org:4040/index.php/user/1") -    # Wrong key -    info_cng = -      User.Info.remote_user_creation(salmon_user.info, %{ -        magic_key: -          "RSA.pu0s-halox4tu7wmES1FVSx6u-4wc0YrUFXcqWXZG4-27UmbCOpMQftRCldNRfyA-qLbz-eqiwrong1EwUvjsD4cYbAHNGHwTvDOyx5AKthQUP44ykPv7kjKGh3DWKySJvcs9tlUG87hlo7AvnMo9pwRS_Zz2CacQ-MKaXyDepk=.AQAB" -      }) +      # Set a wrong magic-key for a user so it has to refetch +      salmon_user = User.get_by_ap_id("http://gs.example.org:4040/index.php/user/1") +      # Wrong key +      info_cng = +        User.Info.remote_user_creation(salmon_user.info, %{ +          magic_key: +            "RSA.pu0s-halox4tu7wmES1FVSx6u-4wc0YrUFXcqWXZG4-27UmbCOpMQftRCldNRfyA-qLbz-eqiwrong1EwUvjsD4cYbAHNGHwTvDOyx5AKthQUP44ykPv7kjKGh3DWKySJvcs9tlUG87hlo7AvnMo9pwRS_Zz2CacQ-MKaXyDepk=.AQAB" +        }) -    salmon_user -    |> Ecto.Changeset.change() -    |> Ecto.Changeset.put_embed(:info, info_cng) -    |> Repo.update() +      salmon_user +      |> Ecto.Changeset.change() +      |> Ecto.Changeset.put_embed(:info, info_cng) +      |> Repo.update() -    conn = -      build_conn() -      |> put_req_header("content-type", "application/atom+xml") -      |> post("/users/#{user.nickname}/salmon", salmon) +      conn = +        build_conn() +        |> put_req_header("content-type", "application/atom+xml") +        |> post("/users/#{user.nickname}/salmon", salmon) -    assert response(conn, 200) +      assert response(conn, 200) +    end    end    test "gets a feed", %{conn: conn} do diff --git a/test/web/ostatus/ostatus_test.exs b/test/web/ostatus/ostatus_test.exs index 403cc7095..9fd100f63 100644 --- a/test/web/ostatus/ostatus_test.exs +++ b/test/web/ostatus/ostatus_test.exs @@ -4,9 +4,13 @@  defmodule Pleroma.Web.OStatusTest do    use Pleroma.DataCase +  alias Pleroma.Activity +  alias Pleroma.Instances +  alias Pleroma.Object +  alias Pleroma.Repo +  alias Pleroma.User    alias Pleroma.Web.OStatus    alias Pleroma.Web.XML -  alias Pleroma.{Object, Repo, User, Activity}    import Pleroma.Factory    import ExUnit.CaptureLog @@ -150,7 +154,7 @@ defmodule Pleroma.Web.OStatusTest do      assert "https://pleroma.soykaf.com/users/lain" in activity.data["to"]      refute activity.local -    retweeted_activity = Repo.get(Activity, retweeted_activity.id) +    retweeted_activity = Activity.get_by_id(retweeted_activity.id)      assert retweeted_activity.data["type"] == "Create"      assert retweeted_activity.data["actor"] == "https://pleroma.soykaf.com/users/lain"      refute retweeted_activity.local @@ -177,7 +181,7 @@ defmodule Pleroma.Web.OStatusTest do      assert user.ap_id in activity.data["to"]      refute activity.local -    retweeted_activity = Repo.get(Activity, retweeted_activity.id) +    retweeted_activity = Activity.get_by_id(retweeted_activity.id)      assert note_activity.id == retweeted_activity.id      assert retweeted_activity.data["type"] == "Create"      assert retweeted_activity.data["actor"] == user.ap_id @@ -311,6 +315,22 @@ defmodule Pleroma.Web.OStatusTest do      refute User.following?(follower, followed)    end +  test "it clears `unreachable` federation status of the sender" do +    incoming_reaction_xml = File.read!("test/fixtures/share-gs.xml") +    doc = XML.parse_document(incoming_reaction_xml) +    actor_uri = XML.string_from_xpath("//author/uri[1]", doc) +    reacted_to_author_uri = XML.string_from_xpath("//author/uri[2]", doc) + +    Instances.set_consistently_unreachable(actor_uri) +    Instances.set_consistently_unreachable(reacted_to_author_uri) +    refute Instances.reachable?(actor_uri) +    refute Instances.reachable?(reacted_to_author_uri) + +    {:ok, _} = OStatus.handle_incoming(incoming_reaction_xml) +    assert Instances.reachable?(actor_uri) +    refute Instances.reachable?(reacted_to_author_uri) +  end +    describe "new remote user creation" do      test "returns local users" do        local_user = insert(:user) @@ -324,7 +344,7 @@ defmodule Pleroma.Web.OStatusTest do        {:ok, user} = OStatus.find_or_make_user(uri) -      user = Repo.get(Pleroma.User, user.id) +      user = Pleroma.User.get_by_id(user.id)        assert user.name == "Constance Variable"        assert user.nickname == "lambadalambda@social.heldscal.la"        assert user.local == false @@ -514,6 +534,8 @@ defmodule Pleroma.Web.OStatusTest do          note_object.data          |> Map.put("type", "Article") +      Cachex.clear(:object_cache) +        cs = Object.change(note_object, %{data: note_data})        {:ok, _article_object} = Repo.update(cs) diff --git a/test/web/push/impl_test.exs b/test/web/push/impl_test.exs new file mode 100644 index 000000000..6bac2c9f6 --- /dev/null +++ b/test/web/push/impl_test.exs @@ -0,0 +1,147 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Push.ImplTest do +  use Pleroma.DataCase + +  alias Pleroma.Web.Push.Impl +  alias Pleroma.Web.Push.Subscription + +  import Pleroma.Factory + +  setup_all do +    Tesla.Mock.mock_global(fn +      %{method: :post, url: "https://example.com/example/1234"} -> +        %Tesla.Env{status: 200} + +      %{method: :post, url: "https://example.com/example/not_found"} -> +        %Tesla.Env{status: 400} + +      %{method: :post, url: "https://example.com/example/bad"} -> +        %Tesla.Env{status: 100} +    end) + +    :ok +  end + +  @sub %{ +    endpoint: "https://example.com/example/1234", +    keys: %{ +      auth: "8eDyX_uCN0XRhSbY5hs7Hg==", +      p256dh: +        "BCIWgsnyXDv1VkhqL2P7YRBvdeuDnlwAPT2guNhdIoW3IP7GmHh1SMKPLxRf7x8vJy6ZFK3ol2ohgn_-0yP7QQA=" +    } +  } +  @api_key "BASgACIHpN1GYgzSRp" +  @message "@Bob: Lorem ipsum dolor sit amet, consectetur  adipiscing elit. Fusce sagittis fini..." + +  test "performs sending notifications" do +    user = insert(:user) +    user2 = insert(:user) +    insert(:push_subscription, user: user, data: %{alerts: %{"mention" => true}}) +    insert(:push_subscription, user: user2, data: %{alerts: %{"mention" => true}}) + +    insert(:push_subscription, +      user: user, +      data: %{alerts: %{"follow" => true, "mention" => true}} +    ) + +    insert(:push_subscription, +      user: user, +      data: %{alerts: %{"follow" => true, "mention" => false}} +    ) + +    notif = +      insert(:notification, +        user: user, +        activity: %Pleroma.Activity{ +          data: %{ +            "type" => "Create", +            "actor" => user.ap_id, +            "object" => %{"content" => "<Lorem ipsum dolor sit amet."} +          } +        } +      ) + +    assert Impl.perform(notif) == [:ok, :ok] +  end + +  @tag capture_log: true +  test "returns error if notif does not match " do +    assert Impl.perform(%{}) == :error +  end + +  test "successful message sending" do +    assert Impl.push_message(@message, @sub, @api_key, %Subscription{}) == :ok +  end + +  @tag capture_log: true +  test "fail message sending" do +    assert Impl.push_message( +             @message, +             Map.merge(@sub, %{endpoint: "https://example.com/example/bad"}), +             @api_key, +             %Subscription{} +           ) == :error +  end + +  test "delete subsciption if restult send message between 400..500" do +    subscription = insert(:push_subscription) + +    assert Impl.push_message( +             @message, +             Map.merge(@sub, %{endpoint: "https://example.com/example/not_found"}), +             @api_key, +             subscription +           ) == :ok + +    refute Pleroma.Repo.get(Subscription, subscription.id) +  end + +  test "renders body for create activity" do +    assert Impl.format_body( +             %{ +               activity: %{ +                 data: %{ +                   "type" => "Create", +                   "object" => %{ +                     "content" => +                       "<span>Lorem ipsum dolor sit amet</span>, consectetur :bear: adipiscing elit. Fusce sagittis finibus turpis." +                   } +                 } +               } +             }, +             %{nickname: "Bob"} +           ) == +             "@Bob: Lorem ipsum dolor sit amet, consectetur  adipiscing elit. Fusce sagittis fini..." +  end + +  test "renders body for follow activity" do +    assert Impl.format_body(%{activity: %{data: %{"type" => "Follow"}}}, %{nickname: "Bob"}) == +             "@Bob has followed you" +  end + +  test "renders body for announce activity" do +    user = insert(:user) + +    note = +      insert(:note, %{ +        data: %{ +          "content" => +            "<span>Lorem ipsum dolor sit amet</span>, consectetur :bear: adipiscing elit. Fusce sagittis finibus turpis." +        } +      }) + +    note_activity = insert(:note_activity, %{note: note}) +    announce_activity = insert(:announce_activity, %{user: user, note_activity: note_activity}) + +    assert Impl.format_body(%{activity: announce_activity}, user) == +             "@#{user.nickname} repeated: Lorem ipsum dolor sit amet, consectetur  adipiscing elit. Fusce sagittis fini..." +  end + +  test "renders body for like activity" do +    assert Impl.format_body(%{activity: %{data: %{"type" => "Like"}}}, %{nickname: "Bob"}) == +             "@Bob has favorited your post" +  end +end diff --git a/test/web/rel_me_test.exs b/test/web/rel_me_test.exs new file mode 100644 index 000000000..5188f4de1 --- /dev/null +++ b/test/web/rel_me_test.exs @@ -0,0 +1,67 @@ +defmodule Pleroma.Web.RelMeTest do +  use ExUnit.Case, async: true + +  setup do +    Tesla.Mock.mock(fn +      %{ +        method: :get, +        url: "http://example.com/rel_me/anchor" +      } -> +        %Tesla.Env{status: 200, body: File.read!("test/fixtures/rel_me_anchor.html")} + +      %{ +        method: :get, +        url: "http://example.com/rel_me/anchor_nofollow" +      } -> +        %Tesla.Env{status: 200, body: File.read!("test/fixtures/rel_me_anchor_nofollow.html")} + +      %{ +        method: :get, +        url: "http://example.com/rel_me/link" +      } -> +        %Tesla.Env{status: 200, body: File.read!("test/fixtures/rel_me_link.html")} + +      %{ +        method: :get, +        url: "http://example.com/rel_me/null" +      } -> +        %Tesla.Env{status: 200, body: File.read!("test/fixtures/rel_me_null.html")} +    end) + +    :ok +  end + +  test "parse/1" do +    hrefs = ["https://social.example.org/users/lain"] + +    assert Pleroma.Web.RelMe.parse("http://example.com/rel_me/null") == {:ok, []} +    assert {:error, _} = Pleroma.Web.RelMe.parse("http://example.com/rel_me/error") + +    assert Pleroma.Web.RelMe.parse("http://example.com/rel_me/link") == {:ok, hrefs} +    assert Pleroma.Web.RelMe.parse("http://example.com/rel_me/anchor") == {:ok, hrefs} +    assert Pleroma.Web.RelMe.parse("http://example.com/rel_me/anchor_nofollow") == {:ok, hrefs} +  end + +  test "maybe_put_rel_me/2" do +    profile_urls = ["https://social.example.org/users/lain"] +    attr = "me" +    fallback = nil + +    assert Pleroma.Web.RelMe.maybe_put_rel_me("http://example.com/rel_me/null", profile_urls) == +             fallback + +    assert Pleroma.Web.RelMe.maybe_put_rel_me("http://example.com/rel_me/error", profile_urls) == +             fallback + +    assert Pleroma.Web.RelMe.maybe_put_rel_me("http://example.com/rel_me/anchor", profile_urls) == +             attr + +    assert Pleroma.Web.RelMe.maybe_put_rel_me( +             "http://example.com/rel_me/anchor_nofollow", +             profile_urls +           ) == attr + +    assert Pleroma.Web.RelMe.maybe_put_rel_me("http://example.com/rel_me/link", profile_urls) == +             attr +  end +end diff --git a/test/web/rich_media/controllers/rich_media_controller_test.exs b/test/web/rich_media/controllers/rich_media_controller_test.exs deleted file mode 100644 index fef126513..000000000 --- a/test/web/rich_media/controllers/rich_media_controller_test.exs +++ /dev/null @@ -1,49 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.RichMedia.RichMediaControllerTest do -  use Pleroma.Web.ConnCase -  import Pleroma.Factory -  import Tesla.Mock - -  setup do -    mock(fn env -> apply(HttpRequestMock, :request, [env]) end) -    :ok -  end - -  describe "GET /api/rich_media/parse" do -    setup do -      user = insert(:user) - -      [user: user] -    end - -    test "returns 404 if not metadata found", %{user: user} do -      build_conn() -      |> with_credentials(user.nickname, "test") -      |> get("/api/rich_media/parse", %{"url" => "http://example.com/empty"}) -      |> json_response(404) -    end - -    test "returns OGP metadata", %{user: user} do -      response = -        build_conn() -        |> with_credentials(user.nickname, "test") -        |> get("/api/rich_media/parse", %{"url" => "http://example.com/ogp"}) -        |> json_response(200) - -      assert response == %{ -               "image" => "http://ia.media-imdb.com/images/rock.jpg", -               "title" => "The Rock", -               "type" => "video.movie", -               "url" => "http://www.imdb.com/title/tt0117500/" -             } -    end -  end - -  defp with_credentials(conn, username, password) do -    header_content = "Basic " <> Base.encode64("#{username}:#{password}") -    put_req_header(conn, "authorization", header_content) -  end -end diff --git a/test/web/rich_media/helpers_test.exs b/test/web/rich_media/helpers_test.exs new file mode 100644 index 000000000..60d93768f --- /dev/null +++ b/test/web/rich_media/helpers_test.exs @@ -0,0 +1,62 @@ +defmodule Pleroma.Web.RichMedia.HelpersTest do +  use Pleroma.DataCase + +  alias Pleroma.Web.CommonAPI + +  import Pleroma.Factory +  import Tesla.Mock + +  setup do +    mock(fn env -> apply(HttpRequestMock, :request, [env]) end) +    :ok +  end + +  test "refuses to crawl incomplete URLs" do +    user = insert(:user) + +    {:ok, activity} = +      CommonAPI.post(user, %{ +        "status" => "[test](example.com/ogp)", +        "content_type" => "text/markdown" +      }) + +    Pleroma.Config.put([:rich_media, :enabled], true) + +    assert %{} == Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) + +    Pleroma.Config.put([:rich_media, :enabled], false) +  end + +  test "refuses to crawl malformed URLs" do +    user = insert(:user) + +    {:ok, activity} = +      CommonAPI.post(user, %{ +        "status" => "[test](example.com[]/ogp)", +        "content_type" => "text/markdown" +      }) + +    Pleroma.Config.put([:rich_media, :enabled], true) + +    assert %{} == Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) + +    Pleroma.Config.put([:rich_media, :enabled], false) +  end + +  test "crawls valid, complete URLs" do +    user = insert(:user) + +    {:ok, activity} = +      CommonAPI.post(user, %{ +        "status" => "[test](http://example.com/ogp)", +        "content_type" => "text/markdown" +      }) + +    Pleroma.Config.put([:rich_media, :enabled], true) + +    assert %{page_url: "http://example.com/ogp", rich_media: _} = +             Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) + +    Pleroma.Config.put([:rich_media, :enabled], false) +  end +end diff --git a/test/web/rich_media/parser_test.exs b/test/web/rich_media/parser_test.exs index e14b5061a..47b127cf9 100644 --- a/test/web/rich_media/parser_test.exs +++ b/test/web/rich_media/parser_test.exs @@ -65,28 +65,31 @@ defmodule Pleroma.Web.RichMedia.ParserTest do      assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/oembed") ==               {:ok,                %{ -                "author_name" => "bees", -                "author_url" => "https://www.flickr.com/photos/bees/", -                "cache_age" => 3600, -                "flickr_type" => "photo", -                "height" => "768", -                "html" => +                author_name: "bees", +                author_url: "https://www.flickr.com/photos/bees/", +                cache_age: 3600, +                flickr_type: "photo", +                height: "768", +                html:                    "<a data-flickr-embed=\"true\" href=\"https://www.flickr.com/photos/bees/2362225867/\" title=\"Bacon Lollys by bees, on Flickr\"><img src=\"https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_b.jpg\" width=\"1024\" height=\"768\" alt=\"Bacon Lollys\"></a><script async src=\"https://embedr.flickr.com/assets/client-code.js\" charset=\"utf-8\"></script>", -                "license" => "All Rights Reserved", -                "license_id" => 0, -                "provider_name" => "Flickr", -                "provider_url" => "https://www.flickr.com/", -                "thumbnail_height" => 150, -                "thumbnail_url" => -                  "https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_q.jpg", -                "thumbnail_width" => 150, -                "title" => "Bacon Lollys", -                "type" => "photo", -                "url" => "https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_b.jpg", -                "version" => "1.0", -                "web_page" => "https://www.flickr.com/photos/bees/2362225867/", -                "web_page_short_url" => "https://flic.kr/p/4AK2sc", -                "width" => "1024" +                license: "All Rights Reserved", +                license_id: 0, +                provider_name: "Flickr", +                provider_url: "https://www.flickr.com/", +                thumbnail_height: 150, +                thumbnail_url: "https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_q.jpg", +                thumbnail_width: 150, +                title: "Bacon Lollys", +                type: "photo", +                url: "https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_b.jpg", +                version: "1.0", +                web_page: "https://www.flickr.com/photos/bees/2362225867/", +                web_page_short_url: "https://flic.kr/p/4AK2sc", +                width: "1024"                }}    end + +  test "rejects invalid OGP data" do +    assert {:error, _} = Pleroma.Web.RichMedia.Parser.parse("http://example.com/malformed") +  end  end diff --git a/test/web/salmon/salmon_test.exs b/test/web/salmon/salmon_test.exs index c539a28b2..35503259b 100644 --- a/test/web/salmon/salmon_test.exs +++ b/test/web/salmon/salmon_test.exs @@ -4,8 +4,10 @@  defmodule Pleroma.Web.Salmon.SalmonTest do    use Pleroma.DataCase +  alias Pleroma.Activity +  alias Pleroma.Repo +  alias Pleroma.User    alias Pleroma.Web.Salmon -  alias Pleroma.{Repo, Activity, User}    import Pleroma.Factory    @magickey "RSA.pu0s-halox4tu7wmES1FVSx6u-4wc0YrUFXcqWXZG4-27UmbCOpMQftRCldNRfyA-qLbz-eqiwQhh-1EwUvjsD4cYbAHNGHwTvDOyx5AKthQUP44ykPv7kjKGh3DWKySJvcs9tlUG87hlo7AvnMo9pwRS_Zz2CacQ-MKaXyDepk=.AQAB" @@ -97,7 +99,7 @@ defmodule Pleroma.Web.Salmon.SalmonTest do      }      {:ok, activity} = Repo.insert(%Activity{data: activity_data, recipients: activity_data["to"]}) -    user = Repo.get_by(User, ap_id: activity.data["actor"]) +    user = User.get_by_ap_id(activity.data["actor"])      {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user)      poster = fn url, _data, _headers -> diff --git a/test/web/streamer_test.exs b/test/web/streamer_test.exs index 16d7b9c24..bfe18cb7f 100644 --- a/test/web/streamer_test.exs +++ b/test/web/streamer_test.exs @@ -5,10 +5,10 @@  defmodule Pleroma.Web.StreamerTest do    use Pleroma.DataCase -  alias Pleroma.Web.Streamer    alias Pleroma.List    alias Pleroma.User    alias Pleroma.Web.CommonAPI +  alias Pleroma.Web.Streamer    import Pleroma.Factory    test "it sends to public" do @@ -39,7 +39,15 @@ defmodule Pleroma.Web.StreamerTest do      task =        Task.async(fn -> -        assert_receive {:text, _}, 4_000 +        expected_event = +          %{ +            "event" => "delete", +            "payload" => activity.id +          } +          |> Jason.encode!() + +        assert_receive {:text, received_event}, 4_000 +        assert received_event == expected_event        end)      fake_socket = %{ @@ -194,4 +202,34 @@ defmodule Pleroma.Web.StreamerTest do      Task.await(task)    end + +  test "it doesn't send muted reblogs" do +    user1 = insert(:user) +    user2 = insert(:user) +    user3 = insert(:user) +    CommonAPI.hide_reblogs(user1, user2) + +    task = +      Task.async(fn -> +        refute_receive {:text, _}, 1_000 +      end) + +    fake_socket = %{ +      transport_pid: task.pid, +      assigns: %{ +        user: user1 +      } +    } + +    {:ok, create_activity} = CommonAPI.post(user3, %{"status" => "I'm kawen"}) +    {:ok, announce_activity, _} = CommonAPI.repeat(create_activity.id, user2) + +    topics = %{ +      "public" => [fake_socket] +    } + +    Streamer.push_to_socket(topics, "public", announce_activity) + +    Task.await(task) +  end  end diff --git a/test/web/twitter_api/representers/activity_representer_test.exs b/test/web/twitter_api/representers/activity_representer_test.exs deleted file mode 100644 index ef0294140..000000000 --- a/test/web/twitter_api/representers/activity_representer_test.exs +++ /dev/null @@ -1,203 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenterTest do -  use Pleroma.DataCase -  alias Pleroma.{User, Activity, Object} -  alias Pleroma.Web.TwitterAPI.Representers.{ActivityRepresenter, ObjectRepresenter} -  alias Pleroma.Web.ActivityPub.ActivityPub -  alias Pleroma.Web.TwitterAPI.UserView -  import Pleroma.Factory - -  test "an announce activity" do -    user = insert(:user) -    note_activity = insert(:note_activity) -    activity_actor = Repo.get_by(User, ap_id: note_activity.data["actor"]) -    object = Object.get_by_ap_id(note_activity.data["object"]["id"]) - -    {:ok, announce_activity, _object} = ActivityPub.announce(user, object) -    note_activity = Activity.get_by_ap_id(note_activity.data["id"]) - -    status = -      ActivityRepresenter.to_map(announce_activity, %{ -        users: [user, activity_actor], -        announced_activity: note_activity, -        for: user -      }) - -    assert status["id"] == announce_activity.id -    assert status["user"] == UserView.render("show.json", %{user: user, for: user}) - -    retweeted_status = -      ActivityRepresenter.to_map(note_activity, %{user: activity_actor, for: user}) - -    assert retweeted_status["repeated"] == true -    assert retweeted_status["id"] == note_activity.id -    assert status["statusnet_conversation_id"] == retweeted_status["statusnet_conversation_id"] - -    assert status["retweeted_status"] == retweeted_status -    assert status["activity_type"] == "repeat" -  end - -  test "a like activity" do -    user = insert(:user) -    note_activity = insert(:note_activity) -    object = Object.get_by_ap_id(note_activity.data["object"]["id"]) - -    {:ok, like_activity, _object} = ActivityPub.like(user, object) - -    status = -      ActivityRepresenter.to_map(like_activity, %{user: user, liked_activity: note_activity}) - -    assert status["id"] == like_activity.id -    assert status["in_reply_to_status_id"] == note_activity.id - -    note_activity = Activity.get_by_ap_id(note_activity.data["id"]) -    activity_actor = Repo.get_by(User, ap_id: note_activity.data["actor"]) -    liked_status = ActivityRepresenter.to_map(note_activity, %{user: activity_actor, for: user}) -    assert liked_status["favorited"] == true -    assert status["activity_type"] == "like" -  end - -  test "an activity" do -    user = insert(:user) -    #   {:ok, mentioned_user } = UserBuilder.insert(%{nickname: "shp", ap_id: "shp"}) -    mentioned_user = insert(:user, %{nickname: "shp"}) - -    # {:ok, follower} = UserBuilder.insert(%{following: [User.ap_followers(user)]}) -    follower = insert(:user, %{following: [User.ap_followers(user)]}) - -    object = %Object{ -      data: %{ -        "type" => "Image", -        "url" => [ -          %{ -            "type" => "Link", -            "mediaType" => "image/jpg", -            "href" => "http://example.org/image.jpg" -          } -        ], -        "uuid" => 1 -      } -    } - -    content_html = -      "<script>alert('YAY')</script>Some :2hu: content mentioning <a href='#{mentioned_user.ap_id}'>@shp</shp>" - -    content = HtmlSanitizeEx.strip_tags(content_html) -    date = DateTime.from_naive!(~N[2016-05-24 13:26:08.003], "Etc/UTC") |> DateTime.to_iso8601() - -    {:ok, convo_object} = Object.context_mapping("2hu") |> Repo.insert() - -    to = [ -      User.ap_followers(user), -      "https://www.w3.org/ns/activitystreams#Public", -      mentioned_user.ap_id -    ] - -    activity = %Activity{ -      id: 1, -      data: %{ -        "type" => "Create", -        "id" => "id", -        "to" => to, -        "actor" => User.ap_id(user), -        "object" => %{ -          "published" => date, -          "type" => "Note", -          "content" => content_html, -          "summary" => "2hu :2hu:", -          "inReplyToStatusId" => 213_123, -          "attachment" => [ -            object -          ], -          "external_url" => "some url", -          "like_count" => 5, -          "announcement_count" => 3, -          "context" => "2hu", -          "tag" => ["content", "mentioning", "nsfw"], -          "emoji" => %{ -            "2hu" => "corndog.png" -          } -        }, -        "published" => date, -        "context" => "2hu" -      }, -      local: false, -      recipients: to -    } - -    expected_html = -      "<p>2hu <img height=\"32px\" width=\"32px\" alt=\"2hu\" title=\"2hu\" src=\"corndog.png\" /></p>alert('YAY')Some <img height=\"32px\" width=\"32px\" alt=\"2hu\" title=\"2hu\" src=\"corndog.png\" /> content mentioning <a href=\"#{ -        mentioned_user.ap_id -      }\">@shp</a>" - -    expected_status = %{ -      "id" => activity.id, -      "user" => UserView.render("show.json", %{user: user, for: follower}), -      "is_local" => false, -      "statusnet_html" => expected_html, -      "text" => "2hu :2hu:" <> content, -      "is_post_verb" => true, -      "created_at" => "Tue May 24 13:26:08 +0000 2016", -      "in_reply_to_status_id" => 213_123, -      "in_reply_to_screen_name" => nil, -      "in_reply_to_user_id" => nil, -      "in_reply_to_profileurl" => nil, -      "in_reply_to_ostatus_uri" => nil, -      "statusnet_conversation_id" => convo_object.id, -      "attachments" => [ -        ObjectRepresenter.to_map(object) -      ], -      "attentions" => [ -        UserView.render("show.json", %{user: mentioned_user, for: follower}) -      ], -      "fave_num" => 5, -      "repeat_num" => 3, -      "favorited" => false, -      "repeated" => false, -      "pinned" => false, -      "external_url" => "some url", -      "tags" => ["nsfw", "content", "mentioning"], -      "activity_type" => "post", -      "possibly_sensitive" => true, -      "uri" => activity.data["object"]["id"], -      "visibility" => "direct", -      "summary" => "2hu :2hu:", -      "summary_html" => -        "2hu <img height=\"32px\" width=\"32px\" alt=\"2hu\" title=\"2hu\" src=\"corndog.png\" />" -    } - -    assert ActivityRepresenter.to_map(activity, %{ -             user: user, -             for: follower, -             mentioned: [mentioned_user] -           }) == expected_status -  end - -  test "an undo for a follow" do -    follower = insert(:user) -    followed = insert(:user) - -    {:ok, _follow} = ActivityPub.follow(follower, followed) -    {:ok, unfollow} = ActivityPub.unfollow(follower, followed) - -    map = ActivityRepresenter.to_map(unfollow, %{user: follower}) -    assert map["is_post_verb"] == false -    assert map["activity_type"] == "undo" -  end - -  test "a delete activity" do -    object = insert(:note) -    user = User.get_by_ap_id(object.data["actor"]) - -    {:ok, delete} = ActivityPub.delete(object) - -    map = ActivityRepresenter.to_map(delete, %{user: user}) - -    assert map["is_post_verb"] == false -    assert map["activity_type"] == "delete" -    assert map["uri"] == object.data["id"] -  end -end diff --git a/test/web/twitter_api/twitter_api_controller_test.exs b/test/web/twitter_api/twitter_api_controller_test.exs index 863abd10f..72b7ea85e 100644 --- a/test/web/twitter_api/twitter_api_controller_test.exs +++ b/test/web/twitter_api/twitter_api_controller_test.exs @@ -4,18 +4,26 @@  defmodule Pleroma.Web.TwitterAPI.ControllerTest do    use Pleroma.Web.ConnCase -  alias Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter -  alias Pleroma.Builders.{ActivityBuilder, UserBuilder} -  alias Pleroma.{Repo, Activity, User, Object, Notification} +  alias Comeonin.Pbkdf2 +  alias Ecto.Changeset +  alias Pleroma.Activity +  alias Pleroma.Builders.ActivityBuilder +  alias Pleroma.Builders.UserBuilder +  alias Pleroma.Notification +  alias Pleroma.Object +  alias Pleroma.Repo +  alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub -  alias Pleroma.Web.TwitterAPI.UserView -  alias Pleroma.Web.TwitterAPI.NotificationView    alias Pleroma.Web.CommonAPI +  alias Pleroma.Web.OAuth.Token +  alias Pleroma.Web.TwitterAPI.ActivityView +  alias Pleroma.Web.TwitterAPI.Controller +  alias Pleroma.Web.TwitterAPI.NotificationView    alias Pleroma.Web.TwitterAPI.TwitterAPI -  alias Comeonin.Pbkdf2 -  alias Ecto.Changeset +  alias Pleroma.Web.TwitterAPI.UserView    import Pleroma.Factory +  import Mock    @banner "data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7" @@ -62,7 +70,8 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do          |> post("/api/account/verify_credentials.json")          |> json_response(200) -      assert response == UserView.render("show.json", %{user: user, token: response["token"]}) +      assert response == +               UserView.render("show.json", %{user: user, token: response["token"], for: user})      end    end @@ -107,7 +116,11 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do          |> post(request_path, %{status: "Nice meme.", visibility: "private"})        assert json_response(conn, 200) == -               ActivityRepresenter.to_map(Repo.one(Activity), %{user: user}) +               ActivityView.render("activity.json", %{ +                 activity: Repo.one(Activity), +                 user: user, +                 for: user +               })      end    end @@ -180,6 +193,20 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do        |> get("/api/statuses/public_timeline.json")        |> json_response(200)      end + +    test_with_mock "treats user as unauthenticated if `assigns[:token]` is present but lacks `read` permission", +                   Controller, +                   [:passthrough], +                   [] do +      token = insert(:oauth_token, scopes: ["write"]) + +      build_conn() +      |> put_req_header("authorization", "Bearer #{token.token}") +      |> get("/api/statuses/public_timeline.json") +      |> json_response(200) + +      assert called(Controller.public_timeline(%{assigns: %{user: nil}}, :_)) +    end    end    describe "GET /statuses/public_and_external_timeline.json" do @@ -250,7 +277,7 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do        response = json_response(conn, 200) -      assert response == ActivityRepresenter.to_map(activity, %{user: actor}) +      assert response == ActivityView.render("activity.json", %{activity: activity, user: actor})      end    end @@ -349,7 +376,8 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do        assert response ==                 Enum.map(returned_activities, fn activity -> -                 ActivityRepresenter.to_map(activity, %{ +                 ActivityView.render("activity.json", %{ +                   activity: activity,                     user: User.get_cached_by_ap_id(activity.data["actor"]),                     for: current_user                   }) @@ -392,6 +420,33 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do        assert status["id"] == direct_two.id        assert status_two["id"] == direct.id      end + +    test "doesn't include DMs from blocked users", %{conn: conn} do +      blocker = insert(:user) +      blocked = insert(:user) +      user = insert(:user) +      {:ok, blocker} = User.block(blocker, blocked) + +      {:ok, _blocked_direct} = +        CommonAPI.post(blocked, %{ +          "status" => "Hi @#{blocker.nickname}!", +          "visibility" => "direct" +        }) + +      {:ok, direct} = +        CommonAPI.post(user, %{ +          "status" => "Hi @#{blocker.nickname}!", +          "visibility" => "direct" +        }) + +      res_conn = +        conn +        |> assign(:user, blocker) +        |> get("/api/statuses/dm_timeline.json") + +      [status] = json_response(res_conn, 200) +      assert status["id"] == direct.id +    end    end    describe "GET /statuses/mentions.json" do @@ -404,7 +459,10 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do      test "with credentials", %{conn: conn, user: current_user} do        {:ok, activity} = -        ActivityBuilder.insert(%{"to" => [current_user.ap_id]}, %{user: current_user}) +        CommonAPI.post(current_user, %{ +          "status" => "why is tenshi eating a corndog so cute?", +          "visibility" => "public" +        })        conn =          conn @@ -416,11 +474,29 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do        assert length(response) == 1        assert Enum.at(response, 0) == -               ActivityRepresenter.to_map(activity, %{ +               ActivityView.render("activity.json", %{                   user: current_user, -                 mentioned: [current_user] +                 for: current_user, +                 activity: activity                 })      end + +    test "does not show DMs in mentions timeline", %{conn: conn, user: current_user} do +      {:ok, _activity} = +        CommonAPI.post(current_user, %{ +          "status" => "Have you guys ever seen how cute tenshi eating a corndog is?", +          "visibility" => "direct" +        }) + +      conn = +        conn +        |> with_credentials(current_user.nickname, "test") +        |> get("/api/statuses/mentions.json") + +      response = json_response(conn, 200) + +      assert Enum.empty?(response) +    end    end    describe "GET /api/qvitter/statuses/notifications.json" do @@ -523,7 +599,9 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do        conn = get(conn, "/api/statuses/user_timeline.json", %{"user_id" => user.id})        response = json_response(conn, 200)        assert length(response) == 1 -      assert Enum.at(response, 0) == ActivityRepresenter.to_map(activity, %{user: user}) + +      assert Enum.at(response, 0) == +               ActivityView.render("activity.json", %{user: user, activity: activity})      end      test "with screen_name", %{conn: conn} do @@ -533,7 +611,9 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do        conn = get(conn, "/api/statuses/user_timeline.json", %{"screen_name" => user.nickname})        response = json_response(conn, 200)        assert length(response) == 1 -      assert Enum.at(response, 0) == ActivityRepresenter.to_map(activity, %{user: user}) + +      assert Enum.at(response, 0) == +               ActivityView.render("activity.json", %{user: user, activity: activity})      end      test "with credentials", %{conn: conn, user: current_user} do @@ -547,7 +627,13 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do        response = json_response(conn, 200)        assert length(response) == 1 -      assert Enum.at(response, 0) == ActivityRepresenter.to_map(activity, %{user: current_user}) + +      assert Enum.at(response, 0) == +               ActivityView.render("activity.json", %{ +                 user: current_user, +                 for: current_user, +                 activity: activity +               })      end      test "with credentials with user_id", %{conn: conn, user: current_user} do @@ -562,7 +648,9 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do        response = json_response(conn, 200)        assert length(response) == 1 -      assert Enum.at(response, 0) == ActivityRepresenter.to_map(activity, %{user: user}) + +      assert Enum.at(response, 0) == +               ActivityView.render("activity.json", %{user: user, activity: activity})      end      test "with credentials screen_name", %{conn: conn, user: current_user} do @@ -577,7 +665,9 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do        response = json_response(conn, 200)        assert length(response) == 1 -      assert Enum.at(response, 0) == ActivityRepresenter.to_map(activity, %{user: user}) + +      assert Enum.at(response, 0) == +               ActivityView.render("activity.json", %{user: user, activity: activity})      end      test "with credentials with user_id, excluding RTs", %{conn: conn, user: current_user} do @@ -596,7 +686,9 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do        response = json_response(conn, 200)        assert length(response) == 1 -      assert Enum.at(response, 0) == ActivityRepresenter.to_map(activity, %{user: user}) + +      assert Enum.at(response, 0) == +               ActivityView.render("activity.json", %{user: user, activity: activity})        conn =          conn @@ -605,7 +697,9 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do        response = json_response(conn, 200)        assert length(response) == 1 -      assert Enum.at(response, 0) == ActivityRepresenter.to_map(activity, %{user: user}) + +      assert Enum.at(response, 0) == +               ActivityView.render("activity.json", %{user: user, activity: activity})      end    end @@ -625,12 +719,29 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do          |> with_credentials(current_user.nickname, "test")          |> post("/api/friendships/create.json", %{user_id: followed.id}) -      current_user = Repo.get(User, current_user.id) +      current_user = User.get_by_id(current_user.id)        assert User.ap_followers(followed) in current_user.following        assert json_response(conn, 200) ==                 UserView.render("show.json", %{user: followed, for: current_user})      end + +    test "for restricted account", %{conn: conn, user: current_user} do +      followed = insert(:user, info: %User.Info{locked: true}) + +      conn = +        conn +        |> with_credentials(current_user.nickname, "test") +        |> post("/api/friendships/create.json", %{user_id: followed.id}) + +      current_user = User.get_by_id(current_user.id) +      followed = User.get_by_id(followed.id) + +      refute User.ap_followers(followed) in current_user.following + +      assert json_response(conn, 200) == +               UserView.render("show.json", %{user: followed, for: current_user}) +    end    end    describe "POST /friendships/destroy.json" do @@ -653,7 +764,7 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do          |> with_credentials(current_user.nickname, "test")          |> post("/api/friendships/destroy.json", %{user_id: followed.id}) -      current_user = Repo.get(User, current_user.id) +      current_user = User.get_by_id(current_user.id)        assert current_user.following == [current_user.ap_id]        assert json_response(conn, 200) == @@ -677,7 +788,7 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do          |> with_credentials(current_user.nickname, "test")          |> post("/api/blocks/create.json", %{user_id: blocked.id}) -      current_user = Repo.get(User, current_user.id) +      current_user = User.get_by_id(current_user.id)        assert User.blocks?(current_user, blocked)        assert json_response(conn, 200) == @@ -704,7 +815,7 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do          |> with_credentials(current_user.nickname, "test")          |> post("/api/blocks/destroy.json", %{user_id: blocked.id}) -      current_user = Repo.get(User, current_user.id) +      current_user = User.get_by_id(current_user.id)        assert current_user.info.blocks == []        assert json_response(conn, 200) == @@ -735,7 +846,7 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do          |> with_credentials(current_user.nickname, "test")          |> post("/api/qvitter/update_avatar.json", %{img: avatar_image}) -      current_user = Repo.get(User, current_user.id) +      current_user = User.get_by_id(current_user.id)        assert is_map(current_user.avatar)        assert json_response(conn, 200) == @@ -843,11 +954,15 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do          |> with_credentials(current_user.nickname, "test")          |> post(request_path) -      activity = Repo.get(Activity, note_activity.id) -      activity_user = Repo.get_by(User, ap_id: note_activity.data["actor"]) +      activity = Activity.get_by_id(note_activity.id) +      activity_user = User.get_by_ap_id(note_activity.data["actor"])        assert json_response(response, 200) == -               ActivityRepresenter.to_map(activity, %{user: activity_user, for: current_user}) +               ActivityView.render("activity.json", %{ +                 user: activity_user, +                 for: current_user, +                 activity: activity +               })      end    end @@ -877,11 +992,15 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do          |> with_credentials(current_user.nickname, "test")          |> post(request_path) -      activity = Repo.get(Activity, note_activity.id) -      activity_user = Repo.get_by(User, ap_id: note_activity.data["actor"]) +      activity = Activity.get_by_id(note_activity.id) +      activity_user = User.get_by_ap_id(note_activity.data["actor"])        assert json_response(response, 200) == -               ActivityRepresenter.to_map(activity, %{user: activity_user, for: current_user}) +               ActivityView.render("activity.json", %{ +                 user: activity_user, +                 for: current_user, +                 activity: activity +               })      end    end @@ -902,7 +1021,7 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do        user = json_response(conn, 200) -      fetched_user = Repo.get_by(User, nickname: "lain") +      fetched_user = User.get_by_nickname("lain")        assert user == UserView.render("show.json", %{user: fetched_user})      end @@ -990,7 +1109,7 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do      test "it confirms the user account", %{conn: conn, user: user} do        get(conn, "/api/account/confirm_email/#{user.id}/#{user.info.confirmation_token}") -      user = Repo.get(User, user.id) +      user = User.get_by_id(user.id)        refute user.info.confirmation_pending        refute user.info.confirmation_token @@ -1132,8 +1251,8 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do               )      end -    test "it returns empty for a hidden network", %{conn: conn} do -      user = insert(:user, %{info: %{hide_network: true}}) +    test "it returns empty when hide_followers is set to true", %{conn: conn} do +      user = insert(:user, %{info: %{hide_followers: true}})        follower_one = insert(:user)        follower_two = insert(:user)        not_follower = insert(:user) @@ -1150,10 +1269,11 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do        assert [] == response      end -    test "it returns the followers for a hidden network if requested by the user themselves", %{ -      conn: conn -    } do -      user = insert(:user, %{info: %{hide_network: true}}) +    test "it returns the followers when hide_followers is set to true if requested by the user themselves", +         %{ +           conn: conn +         } do +      user = insert(:user, %{info: %{hide_followers: true}})        follower_one = insert(:user)        follower_two = insert(:user)        _not_follower = insert(:user) @@ -1208,7 +1328,7 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do        assert Enum.sort(expected) == Enum.sort(result)      end -    test "it returns 20 friends per page", %{conn: conn} do +    test "it returns 20 friends per page, except if 'export' is set to true", %{conn: conn} do        user = insert(:user)        followeds = insert_list(21, :user) @@ -1232,6 +1352,14 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do        result = json_response(res_conn, 200)        assert length(result) == 1 + +      res_conn = +        conn +        |> assign(:user, user) +        |> get("/api/statuses/friends", %{all: true}) + +      result = json_response(res_conn, 200) +      assert length(result) == 21      end      test "it returns a given user's friends with user_id", %{conn: conn} do @@ -1256,8 +1384,8 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do               )      end -    test "it returns empty for a hidden network", %{conn: conn} do -      user = insert(:user, %{info: %{hide_network: true}}) +    test "it returns empty when hide_follows is set to true", %{conn: conn} do +      user = insert(:user, %{info: %{hide_follows: true}})        followed_one = insert(:user)        followed_two = insert(:user)        not_followed = insert(:user) @@ -1273,10 +1401,11 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do        assert [] == json_response(conn, 200)      end -    test "it returns friends for a hidden network if the user themselves request it", %{ -      conn: conn -    } do -      user = insert(:user, %{info: %{hide_network: true}}) +    test "it returns friends when hide_follows is set to true if the user themselves request it", +         %{ +           conn: conn +         } do +      user = insert(:user, %{info: %{hide_follows: true}})        followed_one = insert(:user)        followed_two = insert(:user)        _not_followed = insert(:user) @@ -1364,27 +1493,75 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do        assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user})      end -    test "it sets and un-sets hide_network", %{conn: conn} do +    test "it sets and un-sets hide_follows", %{conn: conn} do +      user = insert(:user) + +      conn +      |> assign(:user, user) +      |> post("/api/account/update_profile.json", %{ +        "hide_follows" => "true" +      }) + +      user = Repo.get!(User, user.id) +      assert user.info.hide_follows == true + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/account/update_profile.json", %{ +          "hide_follows" => "false" +        }) + +      user = Repo.get!(User, user.id) +      assert user.info.hide_follows == false +      assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user}) +    end + +    test "it sets and un-sets hide_followers", %{conn: conn} do +      user = insert(:user) + +      conn +      |> assign(:user, user) +      |> post("/api/account/update_profile.json", %{ +        "hide_followers" => "true" +      }) + +      user = Repo.get!(User, user.id) +      assert user.info.hide_followers == true + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/account/update_profile.json", %{ +          "hide_followers" => "false" +        }) + +      user = Repo.get!(User, user.id) +      assert user.info.hide_followers == false +      assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user}) +    end + +    test "it sets and un-sets show_role", %{conn: conn} do        user = insert(:user)        conn        |> assign(:user, user)        |> post("/api/account/update_profile.json", %{ -        "hide_network" => "true" +        "show_role" => "true"        })        user = Repo.get!(User, user.id) -      assert user.info.hide_network == true +      assert user.info.show_role == true        conn =          conn          |> assign(:user, user)          |> post("/api/account/update_profile.json", %{ -          "hide_network" => "false" +          "show_role" => "false"          })        user = Repo.get!(User, user.id) -      assert user.info.hide_network == false +      assert user.info.show_role == false        assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user})      end @@ -1550,7 +1727,7 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do          })        assert json_response(conn, 200) == %{"status" => "success"} -      fetched_user = Repo.get(User, current_user.id) +      fetched_user = User.get_by_id(current_user.id)        assert Pbkdf2.checkpw("newpass", fetched_user.password_hash) == true      end    end @@ -1591,8 +1768,8 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do        {:ok, _activity} = ActivityPub.follow(other_user, user) -      user = Repo.get(User, user.id) -      other_user = Repo.get(User, other_user.id) +      user = User.get_by_id(user.id) +      other_user = User.get_by_id(other_user.id)        assert User.following?(other_user, user) == false @@ -1604,6 +1781,24 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do        assert [relationship] = json_response(conn, 200)        assert other_user.id == relationship["id"]      end + +    test "requires 'read' permission", %{conn: conn} do +      token1 = insert(:oauth_token, scopes: ["write"]) +      token2 = insert(:oauth_token, scopes: ["read"]) + +      for token <- [token1, token2] do +        conn = +          conn +          |> put_req_header("authorization", "Bearer #{token.token}") +          |> get("/api/pleroma/friend_requests") + +        if token == token1 do +          assert %{"error" => "Insufficient permissions: read."} == json_response(conn, 403) +        else +          assert json_response(conn, 200) +        end +      end +    end    end    describe "POST /api/pleroma/friendships/approve" do @@ -1613,8 +1808,8 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do        {:ok, _activity} = ActivityPub.follow(other_user, user) -      user = Repo.get(User, user.id) -      other_user = Repo.get(User, other_user.id) +      user = User.get_by_id(user.id) +      other_user = User.get_by_id(other_user.id)        assert User.following?(other_user, user) == false @@ -1636,8 +1831,8 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do        {:ok, _activity} = ActivityPub.follow(other_user, user) -      user = Repo.get(User, user.id) -      other_user = Repo.get(User, other_user.id) +      user = User.get_by_id(user.id) +      other_user = User.get_by_id(other_user.id)        assert User.following?(other_user, user) == false @@ -1788,7 +1983,8 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do        user = refresh_record(user) -      assert json_response(response, 200) == ActivityRepresenter.to_map(activity, %{user: user}) +      assert json_response(response, 200) == +               ActivityView.render("activity.json", %{user: user, for: user, activity: activity})      end    end @@ -1817,7 +2013,42 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do        user = refresh_record(user) -      assert json_response(response, 200) == ActivityRepresenter.to_map(activity, %{user: user}) +      assert json_response(response, 200) == +               ActivityView.render("activity.json", %{user: user, for: user, activity: activity}) +    end +  end + +  describe "GET /api/oauth_tokens" do +    setup do +      token = insert(:oauth_token) |> Repo.preload(:user) + +      %{token: token} +    end + +    test "renders list", %{token: token} do +      response = +        build_conn() +        |> assign(:user, token.user) +        |> get("/api/oauth_tokens") + +      keys = +        json_response(response, 200) +        |> hd() +        |> Map.keys() + +      assert keys -- ["id", "app_name", "valid_until"] == [] +    end + +    test "revoke token", %{token: token} do +      response = +        build_conn() +        |> assign(:user, token.user) +        |> delete("/api/oauth_tokens/#{token.id}") + +      tokens = Token.get_user_tokens(token.user) + +      assert tokens == [] +      assert response.status == 201      end    end  end diff --git a/test/web/twitter_api/twitter_api_test.exs b/test/web/twitter_api/twitter_api_test.exs index f94e2b873..a4540e651 100644 --- a/test/web/twitter_api/twitter_api_test.exs +++ b/test/web/twitter_api/twitter_api_test.exs @@ -4,13 +4,23 @@  defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do    use Pleroma.DataCase -  alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView} -  alias Pleroma.{Activity, User, Object, Repo, UserInviteToken} +  alias Pleroma.Activity +  alias Pleroma.Object +  alias Pleroma.Repo +  alias Pleroma.User +  alias Pleroma.UserInviteToken    alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.TwitterAPI.ActivityView +  alias Pleroma.Web.TwitterAPI.TwitterAPI +  alias Pleroma.Web.TwitterAPI.UserView    import Pleroma.Factory +  setup_all do +    Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) +    :ok +  end +    test "create a status" do      user = insert(:user)      mentioned_user = insert(:user, %{nickname: "shp", ap_id: "shp"}) @@ -200,12 +210,27 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do    test "it favorites a status, returns the updated activity" do      user = insert(:user) +    other_user = insert(:user)      note_activity = insert(:note_activity)      {:ok, status} = TwitterAPI.fav(user, note_activity.id)      updated_activity = Activity.get_by_ap_id(note_activity.data["id"]) +    assert ActivityView.render("activity.json", %{activity: updated_activity})["fave_num"] == 1 + +    object = Object.normalize(note_activity.data["object"]) + +    assert object.data["like_count"] == 1      assert status == updated_activity + +    {:ok, _status} = TwitterAPI.fav(other_user, note_activity.id) + +    object = Object.normalize(note_activity.data["object"]) + +    assert object.data["like_count"] == 2 + +    updated_activity = Activity.get_by_ap_id(note_activity.data["id"]) +    assert ActivityView.render("activity.json", %{activity: updated_activity})["fave_num"] == 2    end    test "it unfavorites a status, returns the updated activity" do @@ -255,7 +280,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do      {:ok, user} = TwitterAPI.register_user(data) -    fetched_user = Repo.get_by(User, nickname: "lain") +    fetched_user = User.get_by_nickname("lain")      assert UserView.render("show.json", %{user: user}) ==               UserView.render("show.json", %{user: fetched_user}) @@ -273,13 +298,12 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do      {:ok, user} = TwitterAPI.register_user(data) -    fetched_user = Repo.get_by(User, nickname: "lain") +    fetched_user = User.get_by_nickname("lain")      assert UserView.render("show.json", %{user: user}) ==               UserView.render("show.json", %{user: fetched_user})    end -  @moduletag skip: "needs 'account_activation_required: true' in config"    test "it sends confirmation email if :account_activation_required is specified in instance config" do      setting = Pleroma.Config.get([:instance, :account_activation_required]) @@ -333,68 +357,313 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do      assert user2.bio == expected_text    end -  @moduletag skip: "needs 'registrations_open: false' in config" -  test "it registers a new user via invite token and returns the user." do -    {:ok, token} = UserInviteToken.create_token() +  describe "register with one time token" do +    setup do +      setting = Pleroma.Config.get([:instance, :registrations_open]) -    data = %{ -      "nickname" => "vinny", -      "email" => "pasta@pizza.vs", -      "fullname" => "Vinny Vinesauce", -      "bio" => "streamer", -      "password" => "hiptofbees", -      "confirm" => "hiptofbees", -      "token" => token.token -    } +      if setting do +        Pleroma.Config.put([:instance, :registrations_open], false) +        on_exit(fn -> Pleroma.Config.put([:instance, :registrations_open], setting) end) +      end -    {:ok, user} = TwitterAPI.register_user(data) +      :ok +    end -    fetched_user = Repo.get_by(User, nickname: "vinny") -    token = Repo.get_by(UserInviteToken, token: token.token) +    test "returns user on success" do +      {:ok, invite} = UserInviteToken.create_invite() -    assert token.used == true +      data = %{ +        "nickname" => "vinny", +        "email" => "pasta@pizza.vs", +        "fullname" => "Vinny Vinesauce", +        "bio" => "streamer", +        "password" => "hiptofbees", +        "confirm" => "hiptofbees", +        "token" => invite.token +      } -    assert UserView.render("show.json", %{user: user}) == -             UserView.render("show.json", %{user: fetched_user}) +      {:ok, user} = TwitterAPI.register_user(data) + +      fetched_user = User.get_by_nickname("vinny") +      invite = Repo.get_by(UserInviteToken, token: invite.token) + +      assert invite.used == true + +      assert UserView.render("show.json", %{user: user}) == +               UserView.render("show.json", %{user: fetched_user}) +    end + +    test "returns error on invalid token" do +      data = %{ +        "nickname" => "GrimReaper", +        "email" => "death@reapers.afterlife", +        "fullname" => "Reaper Grim", +        "bio" => "Your time has come", +        "password" => "scythe", +        "confirm" => "scythe", +        "token" => "DudeLetMeInImAFairy" +      } + +      {:error, msg} = TwitterAPI.register_user(data) + +      assert msg == "Invalid token" +      refute User.get_by_nickname("GrimReaper") +    end + +    test "returns error on expired token" do +      {:ok, invite} = UserInviteToken.create_invite() +      UserInviteToken.update_invite!(invite, used: true) + +      data = %{ +        "nickname" => "GrimReaper", +        "email" => "death@reapers.afterlife", +        "fullname" => "Reaper Grim", +        "bio" => "Your time has come", +        "password" => "scythe", +        "confirm" => "scythe", +        "token" => invite.token +      } + +      {:error, msg} = TwitterAPI.register_user(data) + +      assert msg == "Expired token" +      refute User.get_by_nickname("GrimReaper") +    end    end -  @moduletag skip: "needs 'registrations_open: false' in config" -  test "it returns an error if invalid token submitted" do -    data = %{ -      "nickname" => "GrimReaper", -      "email" => "death@reapers.afterlife", -      "fullname" => "Reaper Grim", -      "bio" => "Your time has come", -      "password" => "scythe", -      "confirm" => "scythe", -      "token" => "DudeLetMeInImAFairy" -    } +  describe "registers with date limited token" do +    setup do +      setting = Pleroma.Config.get([:instance, :registrations_open]) + +      if setting do +        Pleroma.Config.put([:instance, :registrations_open], false) +        on_exit(fn -> Pleroma.Config.put([:instance, :registrations_open], setting) end) +      end + +      data = %{ +        "nickname" => "vinny", +        "email" => "pasta@pizza.vs", +        "fullname" => "Vinny Vinesauce", +        "bio" => "streamer", +        "password" => "hiptofbees", +        "confirm" => "hiptofbees" +      } + +      check_fn = fn invite -> +        data = Map.put(data, "token", invite.token) +        {:ok, user} = TwitterAPI.register_user(data) +        fetched_user = User.get_by_nickname("vinny") + +        assert UserView.render("show.json", %{user: user}) == +                 UserView.render("show.json", %{user: fetched_user}) +      end + +      {:ok, data: data, check_fn: check_fn} +    end + +    test "returns user on success", %{check_fn: check_fn} do +      {:ok, invite} = UserInviteToken.create_invite(%{expires_at: Date.utc_today()}) + +      check_fn.(invite) + +      invite = Repo.get_by(UserInviteToken, token: invite.token) -    {:error, msg} = TwitterAPI.register_user(data) +      refute invite.used +    end + +    test "returns user on token which expired tomorrow", %{check_fn: check_fn} do +      {:ok, invite} = UserInviteToken.create_invite(%{expires_at: Date.add(Date.utc_today(), 1)}) + +      check_fn.(invite) + +      invite = Repo.get_by(UserInviteToken, token: invite.token) + +      refute invite.used +    end -    assert msg == "Invalid token" -    refute Repo.get_by(User, nickname: "GrimReaper") +    test "returns an error on overdue date", %{data: data} do +      {:ok, invite} = UserInviteToken.create_invite(%{expires_at: Date.add(Date.utc_today(), -1)}) + +      data = Map.put(data, "token", invite.token) + +      {:error, msg} = TwitterAPI.register_user(data) + +      assert msg == "Expired token" +      refute User.get_by_nickname("vinny") +      invite = Repo.get_by(UserInviteToken, token: invite.token) + +      refute invite.used +    end    end -  @moduletag skip: "needs 'registrations_open: false' in config" -  test "it returns an error if expired token submitted" do -    {:ok, token} = UserInviteToken.create_token() -    UserInviteToken.mark_as_used(token.token) +  describe "registers with reusable token" do +    setup do +      setting = Pleroma.Config.get([:instance, :registrations_open]) -    data = %{ -      "nickname" => "GrimReaper", -      "email" => "death@reapers.afterlife", -      "fullname" => "Reaper Grim", -      "bio" => "Your time has come", -      "password" => "scythe", -      "confirm" => "scythe", -      "token" => token.token -    } +      if setting do +        Pleroma.Config.put([:instance, :registrations_open], false) +        on_exit(fn -> Pleroma.Config.put([:instance, :registrations_open], setting) end) +      end + +      :ok +    end + +    test "returns user on success, after him registration fails" do +      {:ok, invite} = UserInviteToken.create_invite(%{max_use: 100}) + +      UserInviteToken.update_invite!(invite, uses: 99) + +      data = %{ +        "nickname" => "vinny", +        "email" => "pasta@pizza.vs", +        "fullname" => "Vinny Vinesauce", +        "bio" => "streamer", +        "password" => "hiptofbees", +        "confirm" => "hiptofbees", +        "token" => invite.token +      } + +      {:ok, user} = TwitterAPI.register_user(data) +      fetched_user = User.get_by_nickname("vinny") +      invite = Repo.get_by(UserInviteToken, token: invite.token) + +      assert invite.used == true + +      assert UserView.render("show.json", %{user: user}) == +               UserView.render("show.json", %{user: fetched_user}) + +      data = %{ +        "nickname" => "GrimReaper", +        "email" => "death@reapers.afterlife", +        "fullname" => "Reaper Grim", +        "bio" => "Your time has come", +        "password" => "scythe", +        "confirm" => "scythe", +        "token" => invite.token +      } -    {:error, msg} = TwitterAPI.register_user(data) +      {:error, msg} = TwitterAPI.register_user(data) -    assert msg == "Expired token" -    refute Repo.get_by(User, nickname: "GrimReaper") +      assert msg == "Expired token" +      refute User.get_by_nickname("GrimReaper") +    end +  end + +  describe "registers with reusable date limited token" do +    setup do +      setting = Pleroma.Config.get([:instance, :registrations_open]) + +      if setting do +        Pleroma.Config.put([:instance, :registrations_open], false) +        on_exit(fn -> Pleroma.Config.put([:instance, :registrations_open], setting) end) +      end + +      :ok +    end + +    test "returns user on success" do +      {:ok, invite} = UserInviteToken.create_invite(%{expires_at: Date.utc_today(), max_use: 100}) + +      data = %{ +        "nickname" => "vinny", +        "email" => "pasta@pizza.vs", +        "fullname" => "Vinny Vinesauce", +        "bio" => "streamer", +        "password" => "hiptofbees", +        "confirm" => "hiptofbees", +        "token" => invite.token +      } + +      {:ok, user} = TwitterAPI.register_user(data) +      fetched_user = User.get_by_nickname("vinny") +      invite = Repo.get_by(UserInviteToken, token: invite.token) + +      refute invite.used + +      assert UserView.render("show.json", %{user: user}) == +               UserView.render("show.json", %{user: fetched_user}) +    end + +    test "error after max uses" do +      {:ok, invite} = UserInviteToken.create_invite(%{expires_at: Date.utc_today(), max_use: 100}) + +      UserInviteToken.update_invite!(invite, uses: 99) + +      data = %{ +        "nickname" => "vinny", +        "email" => "pasta@pizza.vs", +        "fullname" => "Vinny Vinesauce", +        "bio" => "streamer", +        "password" => "hiptofbees", +        "confirm" => "hiptofbees", +        "token" => invite.token +      } + +      {:ok, user} = TwitterAPI.register_user(data) +      fetched_user = User.get_by_nickname("vinny") +      invite = Repo.get_by(UserInviteToken, token: invite.token) +      assert invite.used == true + +      assert UserView.render("show.json", %{user: user}) == +               UserView.render("show.json", %{user: fetched_user}) + +      data = %{ +        "nickname" => "GrimReaper", +        "email" => "death@reapers.afterlife", +        "fullname" => "Reaper Grim", +        "bio" => "Your time has come", +        "password" => "scythe", +        "confirm" => "scythe", +        "token" => invite.token +      } + +      {:error, msg} = TwitterAPI.register_user(data) + +      assert msg == "Expired token" +      refute User.get_by_nickname("GrimReaper") +    end + +    test "returns error on overdue date" do +      {:ok, invite} = +        UserInviteToken.create_invite(%{expires_at: Date.add(Date.utc_today(), -1), max_use: 100}) + +      data = %{ +        "nickname" => "GrimReaper", +        "email" => "death@reapers.afterlife", +        "fullname" => "Reaper Grim", +        "bio" => "Your time has come", +        "password" => "scythe", +        "confirm" => "scythe", +        "token" => invite.token +      } + +      {:error, msg} = TwitterAPI.register_user(data) + +      assert msg == "Expired token" +      refute User.get_by_nickname("GrimReaper") +    end + +    test "returns error on with overdue date and after max" do +      {:ok, invite} = +        UserInviteToken.create_invite(%{expires_at: Date.add(Date.utc_today(), -1), max_use: 100}) + +      UserInviteToken.update_invite!(invite, uses: 100) + +      data = %{ +        "nickname" => "GrimReaper", +        "email" => "death@reapers.afterlife", +        "fullname" => "Reaper Grim", +        "bio" => "Your time has come", +        "password" => "scythe", +        "confirm" => "scythe", +        "token" => invite.token +      } + +      {:error, msg} = TwitterAPI.register_user(data) + +      assert msg == "Expired token" +      refute User.get_by_nickname("GrimReaper") +    end    end    test "it returns the error on registration problems" do @@ -409,7 +678,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do      {:error, error_object} = TwitterAPI.register_user(data)      assert is_binary(error_object[:error]) -    refute Repo.get_by(User, nickname: "lain") +    refute User.get_by_nickname("lain")    end    test "it assigns an integer conversation_id" do @@ -425,22 +694,6 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do      :ok    end -  describe "context_to_conversation_id" do -    test "creates a mapping object" do -      conversation_id = TwitterAPI.context_to_conversation_id("random context") -      object = Object.get_by_ap_id("random context") - -      assert conversation_id == object.id -    end - -    test "returns an existing mapping for an existing object" do -      {:ok, object} = Object.context_mapping("random context") |> Repo.insert() -      conversation_id = TwitterAPI.context_to_conversation_id("random context") - -      assert conversation_id == object.id -    end -  end -    describe "fetching a user by uri" do      test "fetches a user by uri" do        id = "https://mastodon.social/users/lambadalambda" @@ -452,6 +705,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do        # Also fetches the feed.        # assert Activity.get_create_by_object_ap_id("tag:mastodon.social,2017-04-05:objectId=1641750:objectType=Status") +      # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength      end    end  end diff --git a/test/web/twitter_api/util_controller_test.exs b/test/web/twitter_api/util_controller_test.exs index 73aa70bd5..a4b3d651a 100644 --- a/test/web/twitter_api/util_controller_test.exs +++ b/test/web/twitter_api/util_controller_test.exs @@ -1,8 +1,17 @@  defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do    use Pleroma.Web.ConnCase +  alias Pleroma.Notification +  alias Pleroma.Repo +  alias Pleroma.User +  alias Pleroma.Web.CommonAPI    import Pleroma.Factory +  setup do +    Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end) +    :ok +  end +    describe "POST /api/pleroma/follow_import" do      test "it returns HTTP 200", %{conn: conn} do        user1 = insert(:user) @@ -16,6 +25,25 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do        assert response == "job started"      end + +    test "requires 'follow' permission", %{conn: conn} do +      token1 = insert(:oauth_token, scopes: ["read", "write"]) +      token2 = insert(:oauth_token, scopes: ["follow"]) +      another_user = insert(:user) + +      for token <- [token1, token2] do +        conn = +          conn +          |> put_req_header("authorization", "Bearer #{token.token}") +          |> post("/api/pleroma/follow_import", %{"list" => "#{another_user.ap_id}"}) + +        if token == token1 do +          assert %{"error" => "Insufficient permissions: follow."} == json_response(conn, 403) +        else +          assert json_response(conn, 200) +        end +      end +    end    end    describe "POST /api/pleroma/blocks_import" do @@ -32,4 +60,174 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do        assert response == "job started"      end    end + +  describe "POST /api/pleroma/notifications/read" do +    test "it marks a single notification as read", %{conn: conn} do +      user1 = insert(:user) +      user2 = insert(:user) +      {:ok, activity1} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"}) +      {:ok, activity2} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"}) +      {:ok, [notification1]} = Notification.create_notifications(activity1) +      {:ok, [notification2]} = Notification.create_notifications(activity2) + +      conn +      |> assign(:user, user1) +      |> post("/api/pleroma/notifications/read", %{"id" => "#{notification1.id}"}) +      |> json_response(:ok) + +      assert Repo.get(Notification, notification1.id).seen +      refute Repo.get(Notification, notification2.id).seen +    end +  end + +  describe "PUT /api/pleroma/notification_settings" do +    test "it updates notification settings", %{conn: conn} do +      user = insert(:user) + +      conn +      |> assign(:user, user) +      |> put("/api/pleroma/notification_settings", %{ +        "remote" => false, +        "followers" => false, +        "bar" => 1 +      }) +      |> json_response(:ok) + +      user = Repo.get(User, user.id) + +      assert %{"remote" => false, "local" => true, "followers" => false, "follows" => true} == +               user.info.notification_settings +    end +  end + +  describe "GET /api/statusnet/config.json" do +    test "returns the state of safe_dm_mentions flag", %{conn: conn} do +      option = Pleroma.Config.get([:instance, :safe_dm_mentions]) +      Pleroma.Config.put([:instance, :safe_dm_mentions], true) + +      response = +        conn +        |> get("/api/statusnet/config.json") +        |> json_response(:ok) + +      assert response["site"]["safeDMMentionsEnabled"] == "1" + +      Pleroma.Config.put([:instance, :safe_dm_mentions], false) + +      response = +        conn +        |> get("/api/statusnet/config.json") +        |> json_response(:ok) + +      assert response["site"]["safeDMMentionsEnabled"] == "0" + +      Pleroma.Config.put([:instance, :safe_dm_mentions], option) +    end + +    test "it returns the managed config", %{conn: conn} do +      Pleroma.Config.put([:instance, :managed_config], false) +      Pleroma.Config.put([:fe], theme: "rei-ayanami-towel") + +      response = +        conn +        |> get("/api/statusnet/config.json") +        |> json_response(:ok) + +      refute response["site"]["pleromafe"] + +      Pleroma.Config.put([:instance, :managed_config], true) + +      response = +        conn +        |> get("/api/statusnet/config.json") +        |> json_response(:ok) + +      assert response["site"]["pleromafe"] +    end + +    test "if :pleroma, :fe is false, it returns the new style config settings", %{conn: conn} do +      Pleroma.Config.put([:instance, :managed_config], true) +      Pleroma.Config.put([:fe, :theme], "rei-ayanami-towel") +      Pleroma.Config.put([:frontend_configurations, :pleroma_fe], %{theme: "asuka-hospital"}) + +      response = +        conn +        |> get("/api/statusnet/config.json") +        |> json_response(:ok) + +      assert response["site"]["pleromafe"]["theme"] == "rei-ayanami-towel" + +      Pleroma.Config.put([:fe], false) + +      response = +        conn +        |> get("/api/statusnet/config.json") +        |> json_response(:ok) + +      assert response["site"]["pleromafe"]["theme"] == "asuka-hospital" +    end +  end + +  describe "GET /api/pleroma/frontend_configurations" do +    test "returns everything in :pleroma, :frontend_configurations", %{conn: conn} do +      config = [ +        frontend_a: %{ +          x: 1, +          y: 2 +        }, +        frontend_b: %{ +          z: 3 +        } +      ] + +      Pleroma.Config.put(:frontend_configurations, config) + +      response = +        conn +        |> get("/api/pleroma/frontend_configurations") +        |> json_response(:ok) + +      assert response == Jason.encode!(config |> Enum.into(%{})) |> Jason.decode!() +    end +  end + +  describe "/api/pleroma/emoji" do +    test "returns json with custom emoji with tags", %{conn: conn} do +      emoji = +        conn +        |> get("/api/pleroma/emoji") +        |> json_response(200) + +      assert Enum.all?(emoji, fn +               {_key, +                %{ +                  "image_url" => url, +                  "tags" => tags +                }} -> +                 is_binary(url) and is_list(tags) +             end) +    end +  end + +  describe "GET /ostatus_subscribe?acct=...." do +    test "adds status to pleroma instance if the `acct` is a status", %{conn: conn} do +      conn = +        get( +          conn, +          "/ostatus_subscribe?acct=https://mastodon.social/users/emelie/statuses/101849165031453009" +        ) + +      assert redirected_to(conn) =~ "/notice/" +    end + +    test "show follow account page if the `acct` is a account link", %{conn: conn} do +      response = +        get( +          conn, +          "/ostatus_subscribe?acct=https://mastodon.social/users/emelie" +        ) + +      assert html_response(response, 200) =~ "Log in to follow" +    end +  end  end diff --git a/test/web/twitter_api/views/activity_view_test.exs b/test/web/twitter_api/views/activity_view_test.exs index ba053d20d..ee9a0c834 100644 --- a/test/web/twitter_api/views/activity_view_test.exs +++ b/test/web/twitter_api/views/activity_view_test.exs @@ -5,15 +5,14 @@  defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do    use Pleroma.DataCase +  alias Pleroma.Activity +  alias Pleroma.Repo +  alias Pleroma.User +  alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.CommonAPI    alias Pleroma.Web.CommonAPI.Utils    alias Pleroma.Web.TwitterAPI.ActivityView    alias Pleroma.Web.TwitterAPI.UserView -  alias Pleroma.Web.TwitterAPI.TwitterAPI -  alias Pleroma.Repo -  alias Pleroma.Activity -  alias Pleroma.User -  alias Pleroma.Web.ActivityPub.ActivityPub    import Pleroma.Factory    import Tesla.Mock @@ -56,6 +55,22 @@ defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do      assert result["user"]["id"] == user.id    end +  test "tells if the message is muted for some reason" do +    user = insert(:user) +    other_user = insert(:user) + +    {:ok, user} = User.mute(user, other_user) + +    {:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"}) +    status = ActivityView.render("activity.json", %{activity: activity}) + +    assert status["muted"] == false + +    status = ActivityView.render("activity.json", %{activity: activity, for: user}) + +    assert status["muted"] == true +  end +    test "a create activity with a html status" do      text = """      #Bike log - Commute Tuesday\nhttps://pla.bike/posts/20181211/\n#cycling #CHScycling #commute\nMVIMG_20181211_054020.jpg @@ -66,7 +81,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do      result = ActivityView.render("activity.json", activity: activity)      assert result["statusnet_html"] == -             "<a class=\"hashtag\" data-tag=\"bike\" href=\"http://localhost:4001/tag/bike\">#Bike</a> log - Commute Tuesday<br /><a href=\"https://pla.bike/posts/20181211/\">https://pla.bike/posts/20181211/</a><br /><a class=\"hashtag\" data-tag=\"cycling\" href=\"http://localhost:4001/tag/cycling\">#cycling</a> <a class=\"hashtag\" data-tag=\"chscycling\" href=\"http://localhost:4001/tag/chscycling\">#CHScycling</a> <a class=\"hashtag\" data-tag=\"commute\" href=\"http://localhost:4001/tag/commute\">#commute</a><br />MVIMG_20181211_054020.jpg" +             "<a class=\"hashtag\" data-tag=\"bike\" href=\"http://localhost:4001/tag/bike\" rel=\"tag\">#Bike</a> log - Commute Tuesday<br /><a href=\"https://pla.bike/posts/20181211/\">https://pla.bike/posts/20181211/</a><br /><a class=\"hashtag\" data-tag=\"cycling\" href=\"http://localhost:4001/tag/cycling\" rel=\"tag\">#cycling</a> <a class=\"hashtag\" data-tag=\"chscycling\" href=\"http://localhost:4001/tag/chscycling\" rel=\"tag\">#CHScycling</a> <a class=\"hashtag\" data-tag=\"commute\" href=\"http://localhost:4001/tag/commute\" rel=\"tag\">#commute</a><br />MVIMG_20181211_054020.jpg"      assert result["text"] ==               "#Bike log - Commute Tuesday\nhttps://pla.bike/posts/20181211/\n#cycling #CHScycling #commute\nMVIMG_20181211_054020.jpg" @@ -113,7 +128,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do      result = ActivityView.render("activity.json", activity: activity) -    convo_id = TwitterAPI.context_to_conversation_id(activity.data["object"]["context"]) +    convo_id = Utils.context_to_conversation_id(activity.data["object"]["context"])      expected = %{        "activity_type" => "post", @@ -148,7 +163,9 @@ defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do        "text" => "Hey @shp!",        "uri" => activity.data["object"]["id"],        "user" => UserView.render("show.json", %{user: user}), -      "visibility" => "direct" +      "visibility" => "direct", +      "card" => nil, +      "muted" => false      }      assert result == expected @@ -159,12 +176,12 @@ defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do      other_user = insert(:user, %{nickname: "shp"})      {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!"}) -    convo_id = TwitterAPI.context_to_conversation_id(activity.data["object"]["context"]) +    convo_id = Utils.context_to_conversation_id(activity.data["object"]["context"])      mocks = [        { -        TwitterAPI, -        [], +        Utils, +        [:passthrough],          [context_to_conversation_id: fn _ -> false end]        },        { @@ -179,7 +196,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do        assert result["statusnet_conversation_id"] == convo_id        assert result["user"] -      refute called(TwitterAPI.context_to_conversation_id(:_)) +      refute called(Utils.context_to_conversation_id(:_))        refute called(User.get_cached_by_ap_id(user.ap_id))        refute called(User.get_cached_by_ap_id(other_user.ap_id))      end @@ -262,9 +279,9 @@ defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do      {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!"})      {:ok, announce, _object} = CommonAPI.repeat(activity.id, other_user) -    convo_id = TwitterAPI.context_to_conversation_id(activity.data["object"]["context"]) +    convo_id = Utils.context_to_conversation_id(activity.data["object"]["context"]) -    activity = Repo.get(Activity, activity.id) +    activity = Activity.get_by_id(activity.id)      result = ActivityView.render("activity.json", activity: announce) diff --git a/test/web/twitter_api/views/notification_view_test.exs b/test/web/twitter_api/views/notification_view_test.exs index 8367fc6c7..6baeeaf63 100644 --- a/test/web/twitter_api/views/notification_view_test.exs +++ b/test/web/twitter_api/views/notification_view_test.exs @@ -5,13 +5,14 @@  defmodule Pleroma.Web.TwitterAPI.NotificationViewTest do    use Pleroma.DataCase -  alias Pleroma.{User, Notification} -  alias Pleroma.Web.TwitterAPI.TwitterAPI +  alias Pleroma.Notification +  alias Pleroma.User +  alias Pleroma.Web.ActivityPub.ActivityPub +  alias Pleroma.Web.CommonAPI.Utils +  alias Pleroma.Web.TwitterAPI.ActivityView    alias Pleroma.Web.TwitterAPI.NotificationView +  alias Pleroma.Web.TwitterAPI.TwitterAPI    alias Pleroma.Web.TwitterAPI.UserView -  alias Pleroma.Web.TwitterAPI.ActivityView -  alias Pleroma.Web.CommonAPI.Utils -  alias Pleroma.Web.ActivityPub.ActivityPub    import Pleroma.Factory diff --git a/test/web/twitter_api/views/user_view_test.exs b/test/web/twitter_api/views/user_view_test.exs index daf18c1c5..0feaf4b64 100644 --- a/test/web/twitter_api/views/user_view_test.exs +++ b/test/web/twitter_api/views/user_view_test.exs @@ -6,8 +6,8 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do    use Pleroma.DataCase    alias Pleroma.User -  alias Pleroma.Web.TwitterAPI.UserView    alias Pleroma.Web.CommonAPI.Utils +  alias Pleroma.Web.TwitterAPI.UserView    import Pleroma.Factory @@ -100,7 +100,8 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do        "locked" => false,        "default_scope" => "public",        "no_rich_text" => false, -      "hide_network" => false, +      "hide_follows" => false, +      "hide_followers" => false,        "fields" => [],        "pleroma" => %{          "confirmation_pending" => false, @@ -147,7 +148,8 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do        "locked" => false,        "default_scope" => "public",        "no_rich_text" => false, -      "hide_network" => false, +      "hide_follows" => false, +      "hide_followers" => false,        "fields" => [],        "pleroma" => %{          "confirmation_pending" => false, @@ -195,7 +197,8 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do        "locked" => false,        "default_scope" => "public",        "no_rich_text" => false, -      "hide_network" => false, +      "hide_follows" => false, +      "hide_followers" => false,        "fields" => [],        "pleroma" => %{          "confirmation_pending" => false, @@ -211,6 +214,7 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do      represented = UserView.render("show.json", %{user: user, for: user})      assert represented["rights"]["delete_others_notice"] +    assert represented["role"] == "moderator"    end    test "a user that is a admin" do @@ -218,6 +222,28 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do      represented = UserView.render("show.json", %{user: user, for: user})      assert represented["rights"]["admin"] +    assert represented["role"] == "admin" +  end + +  test "A moderator with hidden role for another user", %{user: user} do +    admin = insert(:user, %{info: %{is_moderator: true, show_role: false}}) +    represented = UserView.render("show.json", %{user: admin, for: user}) + +    assert represented["role"] == nil +  end + +  test "An admin with hidden role for another user", %{user: user} do +    admin = insert(:user, %{info: %{is_admin: true, show_role: false}}) +    represented = UserView.render("show.json", %{user: admin, for: user}) + +    assert represented["role"] == nil +  end + +  test "A regular user for the admin", %{user: user} do +    admin = insert(:user, %{info: %{is_admin: true}}) +    represented = UserView.render("show.json", %{user: user, for: admin}) + +    assert represented["pleroma"]["deactivated"] == false    end    test "A blocked user for the blocker" do @@ -257,7 +283,8 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do        "locked" => false,        "default_scope" => "public",        "no_rich_text" => false, -      "hide_network" => false, +      "hide_follows" => false, +      "hide_followers" => false,        "fields" => [],        "pleroma" => %{          "confirmation_pending" => false, @@ -265,7 +292,7 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do        }      } -    blocker = Repo.get(User, blocker.id) +    blocker = User.get_by_id(blocker.id)      assert represented == UserView.render("show.json", %{user: user, for: blocker})    end diff --git a/test/web/views/error_view_test.exs b/test/web/views/error_view_test.exs index 16a0c8cef..d529fd2c3 100644 --- a/test/web/views/error_view_test.exs +++ b/test/web/views/error_view_test.exs @@ -14,11 +14,16 @@ defmodule Pleroma.Web.ErrorViewTest do    test "render 500.json" do      assert render(Pleroma.Web.ErrorView, "500.json", []) == -             %{errors: %{detail: "Internal server error"}} +             %{errors: %{detail: "Internal server error", reason: "nil"}}    end    test "render any other" do      assert render(Pleroma.Web.ErrorView, "505.json", []) == -             %{errors: %{detail: "Internal server error"}} +             %{errors: %{detail: "Internal server error", reason: "nil"}} +  end + +  test "render 500.json with reason" do +    assert render(Pleroma.Web.ErrorView, "500.json", reason: "test reason") == +             %{errors: %{detail: "Internal server error", reason: "\"test reason\""}}    end  end diff --git a/test/web/websub/websub_controller_test.exs b/test/web/websub/websub_controller_test.exs index 9cbcda063..1e69ed01a 100644 --- a/test/web/websub/websub_controller_test.exs +++ b/test/web/websub/websub_controller_test.exs @@ -5,9 +5,10 @@  defmodule Pleroma.Web.Websub.WebsubControllerTest do    use Pleroma.Web.ConnCase    import Pleroma.Factory -  alias Pleroma.Web.Websub.WebsubClientSubscription -  alias Pleroma.{Repo, Activity} +  alias Pleroma.Activity +  alias Pleroma.Repo    alias Pleroma.Web.Websub +  alias Pleroma.Web.Websub.WebsubClientSubscription    test "websub subscription request", %{conn: conn} do      user = insert(:user) @@ -50,35 +51,37 @@ defmodule Pleroma.Web.Websub.WebsubControllerTest do      assert_in_delta NaiveDateTime.diff(websub.valid_until, NaiveDateTime.utc_now()), 100, 5    end -  test "handles incoming feed updates", %{conn: conn} do -    websub = insert(:websub_client_subscription) -    doc = "some stuff" -    signature = Websub.sign(websub.secret, doc) +  describe "websub_incoming" do +    test "handles incoming feed updates", %{conn: conn} do +      websub = insert(:websub_client_subscription) +      doc = "some stuff" +      signature = Websub.sign(websub.secret, doc) -    conn = -      conn -      |> put_req_header("x-hub-signature", "sha1=" <> signature) -      |> put_req_header("content-type", "application/atom+xml") -      |> post("/push/subscriptions/#{websub.id}", doc) +      conn = +        conn +        |> put_req_header("x-hub-signature", "sha1=" <> signature) +        |> put_req_header("content-type", "application/atom+xml") +        |> post("/push/subscriptions/#{websub.id}", doc) -    assert response(conn, 200) == "OK" +      assert response(conn, 200) == "OK" -    assert length(Repo.all(Activity)) == 1 -  end +      assert length(Repo.all(Activity)) == 1 +    end -  test "rejects incoming feed updates with the wrong signature", %{conn: conn} do -    websub = insert(:websub_client_subscription) -    doc = "some stuff" -    signature = Websub.sign("wrong secret", doc) +    test "rejects incoming feed updates with the wrong signature", %{conn: conn} do +      websub = insert(:websub_client_subscription) +      doc = "some stuff" +      signature = Websub.sign("wrong secret", doc) -    conn = -      conn -      |> put_req_header("x-hub-signature", "sha1=" <> signature) -      |> put_req_header("content-type", "application/atom+xml") -      |> post("/push/subscriptions/#{websub.id}", doc) +      conn = +        conn +        |> put_req_header("x-hub-signature", "sha1=" <> signature) +        |> put_req_header("content-type", "application/atom+xml") +        |> post("/push/subscriptions/#{websub.id}", doc) -    assert response(conn, 500) == "Error" +      assert response(conn, 500) == "Error" -    assert length(Repo.all(Activity)) == 0 +      assert Enum.empty?(Repo.all(Activity)) +    end    end  end diff --git a/test/web/websub/websub_test.exs b/test/web/websub/websub_test.exs index 9751d161d..74386d7db 100644 --- a/test/web/websub/websub_test.exs +++ b/test/web/websub/websub_test.exs @@ -4,10 +4,13 @@  defmodule Pleroma.Web.WebsubTest do    use Pleroma.DataCase + +  alias Pleroma.Web.Router.Helpers    alias Pleroma.Web.Websub -  alias Pleroma.Web.Websub.{WebsubServerSubscription, WebsubClientSubscription} +  alias Pleroma.Web.Websub.WebsubClientSubscription +  alias Pleroma.Web.Websub.WebsubServerSubscription +    import Pleroma.Factory -  alias Pleroma.Web.Router.Helpers    import Tesla.Mock    setup do  | 
