[u]underlined[/u]: underlined +
BBCode | +Result | +
---|---|
[b]bold[/b] | +bold | +
[i]italic[/i] | +italic | +
[u]underlined[/u] | +underlined | +
[s]strike[/s] | +|
[o]overline[/o] | +overline | +
[color=red]red[/color] | +red | +
[url=http://www.friendica.com]Friendica[/url] | +Friendica | +
[img]http://friendica.com/sites/default/files/friendika-32.png[/img] | ++ |
[img=64x32]http://friendica.com/sites/default/files/friendika-32.png[/img] + Note: provided height is simply discarded. |
+ + |
[size=xx-small]small text[/size] | +small text | +
[size=xx-large]big text[/size] | +big text | +
[size=20]exact size[/size] (size can be any number, in pixel) | +exact size | +
[font=serif]Serif font[/font] | +Serif font | +
[s]strike[/s]:
[color=red]red[/color]: red +
BBCode | +Result | +
---|---|
[url]http://friendica.com[/url] | +http://friendica.com | +
[url=http://friendica.com]Friendica[/url] | +Friendica | +
[bookmark]http://friendica.com[/bookmark] +#^[url]http://friendica.com[/url] |
+ + |
[bookmark=http://friendica.com]Bookmark[/bookmark] +#^[url=http://friendica.com]Bookmark[/url] +#[url=http://friendica.com]^[/url][url=http://friendica.com]Bookmark[/url] |
+ + |
[url=/posts/f16d77b0630f0134740c0cc47a0ea02a]Diaspora post with GUID[/url] | +Diaspora post with GUID | +
#Friendica | +#Friendica | +
@Mention | +@Mention | +
acct:account@friendica.host.com (WebFinger) | +acct:account@friendica.host.com | +
[mail]user@mail.example.com[/mail] | +user@mail.example.com | +
[mail=user@mail.example.com]Send an email to User[/mail] | +Send an email to User | +
[url=http://www.friendica.com]Friendica[/url]: Friendica +## Blocks -
[img]http://friendica.com/sites/default/files/friendika-32.png[/img]: +
BBCode | +Result | +
---|---|
[p]A paragraph of text[/p] | +A paragraph of text |
+
Inline [code]code[/code] in a paragraph | +Inline |
+
[code]Multi line code[/code] |
+ Multi
+line
+code |
+
[code=php]function text_highlight($s,$lang)[/code] | +
|
+
[quote]quote[/quote] | +quote |
+
[quote=Author]Author? Me? No, no, no...[/quote] | +Author wrote:Author? Me? No, no, no... |
+
[center]Centered text[/center] | +Centered text |
+
You should not read any further if you want to be surprised.[spoiler]There is a happy end.[/spoiler] | +
+
+ You should not read any further if you want to be surprised.
+ + Click to open/close + +
+ |
+
[spoiler=Author]Spoiler quote[/spoiler] | +
+
+ Author wrote:
+ + Click to open/close + +
+ |
+
[hr] (horizontal line) | +
[size=xx-small]small text[/size]: small text +### Titles -
[size=xx-large]big text[/size]: big text +
BBCode | +Result | +
---|---|
[h1]Title 1[/h1] | +Title 1 |
+
[h2]Title 2[/h2] | +Title 2 |
+
[h3]Title 3[/h3] | +Title 3 |
+
[h4]Title 4[/h4] | +Title 4 |
+
[h5]Title 5[/h5] | +Title 5 |
+
[h6]Title 6[/h6] | +Title 6 |
+
[size=20]exact size[/size] (size can be any number, in pixel): exact size +### Tables +
BBCode | +Result | +|||||||||
---|---|---|---|---|---|---|---|---|---|---|
[table] + [tr] + [th]Header 1[/th] + [th]Header 2[/th] + [th]Header 2[/th] + [/tr] + [tr] + [td]Cell 1[/td] + [td]Cell 2[/td] + [td]Cell 3[/td] + [/tr] + [tr] + [td]Cell 4[/td] + [td]Cell 5[/td] + [td]Cell 6[/td] + [/tr] +[/table] |
+
+
|
+|||||||||
[table border=0] | +
+
|
+|||||||||
[table border=1] | +
+
|
+
BBCode | +Result | +
---|---|
[ul] + [li] First list element + [li] Second list element +[/ul] +[list] + [*] First list element + [*] Second list element +[/list] |
+
+
|
+
[ol] + [*] First list element + [*] Second list element +[/ol] +[list=1] + [*] First list element + [*] Second list element +[/list] |
+
+
|
+
[list=] + [*] First list element + [*] Second list element +[/list] |
+
+
|
+
[list=i] + [*] First list element + [*] Second list element +[/list] |
+
+
|
+
[list=I] + [*] First list element + [*] Second list element +[/list] |
+
+
|
+
[list=a] + [*] First list element + [*] Second list element +[/list] |
+
+
|
+
[list=A] + [*] First list element + [*] Second list element +[/list] |
+
+
|
+
[code]code[/code]- -
code
-
-- -
[quote]quote[/quote]- -
quote- -
- -
[quote=Author]Author? Me? No, no, no...[/quote]- -Author wrote:
Author? Me? No, no, no...- -
- -
[center]centered text[/center]- -
- -
You should not read any further if you want to be surprised.[spoiler]There is a happy end.[/spoiler]- -You should not read any further if you want to be surprised.
- -**Table** -
[table border=1] - [tr] - [th]Tables now[/th] - [/tr] - [tr] - [td]Have headers[/td] - [/tr] -[/table]- -
Tables now |
---|
Have headers |
- -**List** - -
[list] - [*] First list element - [*] Second list element -[/list]-
[ol] - [*] First list element - [*] Second list element -[/ol]-
[list=1]: decimal - -
[list=i]: lover case roman - -
[list=I]: upper case roman - -
[list=a]: lover case alphabetic - -
[list=A]: upper case alphabetic - - - - -Embed ------- +## Embed You can embed video, audio and more in a message. -
[video]url[/video]-
[audio]url[/audio]+
BBCode | +Result | +
---|---|
[video]url[/video] | +Where *url* can be an url to youtube, vimeo, soundcloud, or other sites wich supports oembed or opengraph specifications. | +
[video]Video file url[/video] +[audio]Audio file url[/audio] | +Full URL to an ogg/ogv/oga/ogm/webm/mp4/mp3 file. An HTML5 player will be used to show it. | +
[youtube]Youtube URL[/youtube] | +Youtube video OEmbed display. May not embed an actual player. | +
[youtube]Youtube video ID[/youtube] | +Youtube player iframe embed. | +
[vimeo]Vimeo URL[/vimeo] | +Vimeo video OEmbed display. May not embed an actual player. | +
[vimeo]Vimeo video ID[/vimeo] | +Vimeo player iframe embed. | +
[embed]URL[/embed] | +Embed OEmbed rich content. | +
[iframe]URL[/iframe] | +General embed, iframe size is limited by the theme size for video players. | +
[url]*url*[/url] | +If *url* supports oembed or opengraph specifications the embedded object will be shown (eg, documents from scribd). +Page title with a link to *url* will be shown. | +
[url]*url*[/url]+This require "openstreetmap" or "Google Maps" addon version 1.3 or newer. +If the addon isn't activated, the raw coordinates are shown instead. -If *url* supports oembed or opengraph specifications the embedded object will be shown (eg, documents from scribd). -Page title with a link to *url* will be shown. +
BBCode | +Result | +
---|---|
[map]address[/map] | +Embeds a map centered on this address. | +
[map=lat,long] | +Embeds a map centered on those coordinates. | +
[map] | +Embeds a map centered on the post's location. | +
[map]address[/map]-
[map=lat,long]+If you want to spread your post to several third party networks you can have the problem that these networks have a length limitation like on Twitter. -You can embed maps from coordinates or addresses. -This require "openstreetmap" addon version 1.3 or newer. - ------------------------------------------------------------ - -Abstract for longer posts -------------------------- - -If you want to spread your post to several third party networks you can have the problem that these networks have (for example) a length limitation. -(Like on Twitter) - -Friendica is using a semi intelligent mechanism to generate a fitting abstract. -But it can be interesting to define an own abstract that will only be displayed on the external network. -This is done with the [abstract]-element. -Example: - -
[abstract]Totally interesting! A must-see! Please click the link![/abstract] -I want to tell you a really boring story that you really never wanted -to hear.- -Twitter would display the text "Totally interesting! A must-see! Please click the link!". -On Friendica you would only see the text after "I want to tell you a really ..." +Friendica is using a semi intelligent mechanism to generate a fitting abstract. +But it can be interesting to define a custom abstract that will only be displayed on the external network. +This is done with the [abstract]-element. +
BBCode | +Result | +
---|---|
[abstract]Totally interesting! A must-see! Please click the link![/abstract] +I want to tell you a really boring story that you really never wanted to hear. |
+ Twitter would display the text Totally interesting! A must-see! Please click the link!+On Friendica you would only see the text after I want to tell you a really ... |
+
-[abstract]Hi friends Here are my newest pictures![abstract] -[abstract=twit]Hi my dear Twitter followers. Do you want to see my new -pictures?[abstract] -[abstract=apdn]Helly my dear followers on ADN. I made sone new pictures -that I wanted to share with you.[abstract] -Today I was in the woods and took some real cool pictures ... -- -For Twitter and App.net the system will use the defined abstracts. -For other networks (e.g. when you are using the "statusnet" connector that is used to post to GNU Social) the general abstract element will be used. +
BBCode | +Result | +
---|---|
+[abstract]Hi friends Here are my newest pictures![/abstract] +[abstract=twit]Hi my dear Twitter followers. Do you want to see my new +pictures?[/abstract] +[abstract=apdn]Helly my dear followers on ADN. I made sone new pictures +that I wanted to share with you.[/abstract] +Today I was in the woods and took some real cool pictures ... |
+ For Twitter and App.net the system will use the defined abstracts. +For other networks (e.g. when you are using the "statusnet" connector that is used to post to your GNU Social account) the general abstract element will be used. |
+
-[abstract]These days I had a strange encounter ...[abstract] -[abstract=goog]Helly my dear Google+ followers. You have to read my -newest blog post![abstract] -[abstract=face]Hello my Facebook friends. These days happened something -really cool.[abstract] -While taking pictures in the woods I had a really strange encounter ...+
BBCode | +Result | +
---|---|
+[abstract]These days I had a strange encounter...[/abstract] +[abstract=goog]Helly my dear Google+ followers. You have to read my newest blog post![/abstract] +[abstract=face]Hello my Facebook friends. These days happened something really cool.[/abstract] +While taking pictures in the woods I had a really strange encounter... |
+ Google and Facebook will show the respective abstracts while the other networks will show the default one. + Meanwhile, Friendica won't show any of the abstracts. |
+
[noparse][b]bold[/b][/noparse]: [b]bold[/b] +
BBCode | +Result | +
---|---|
If you need to put literal bbcode in a message, [noparse], [nobb] or [pre] are used to escape bbcode:
+
|
+ [b]bold[/b] | +
[nosmile] is used to disable smilies on a post by post basis + + [nosmile] ;-) :-O + |
+ ;-) :-O | +
Custom inline styles + +[style=text-shadow: 0 0 4px #CC0000;]You can change all the CSS properties of this block.[/style] |
+ You can change all the CSS properties of this block. | +
Custom class block + +[class=custom]If the class exists, this block will have the custom class style applied.[/class] |
+ <span class="custom">If the class exists, |
+
BBCode | +Ergebnis | +
---|---|
[b]fett[/b] | +fett | +
[i]kursiv[/i] | +kursiv | +
[u]unterstrichen[/u] | +unterstrichen | +
[s]durchgestrichen[/s] | +|
[o]überstrichen[/o] | +überstrichen | +
[color=red]rot[/color] | +rot | +
[url=http://www.friendica.com]Friendica[/url] | +Friendica | +
[img]http://friendica.com/sites/default/files/friendika-32.png[/img] | ++ |
[img=64x32]http://friendica.com/sites/default/files/friendika-32.png[/img] + Note: provided height is simply discarded. |
+ + |
[size=xx-small]kleiner Text[/size] | +kleiner Text | +
[size=xx-large]großer Text[/size] | +großer Text | +
[size=20]exakte Größe[/size] (die Größe kann beliebig in Pixeln gewält werden) | +exakte Größe | +
[font=serif]Serife Schriftart[/font] | +Serife Schriftart | +
BBCode | +Ergebnis | +
---|---|
[url]http://friendica.com[/url] | +http://friendica.com | +
[url=http://friendica.com]Friendica[/url] | +Friendica | +
[bookmark]http://friendica.com[/bookmark] +#^[url]http://friendica.com[/url] |
+ + |
[bookmark=http://friendica.com]Lesezeichen[/bookmark] +#^[url=http://friendica.com]Lesezeichen[/url] +#[url=http://friendica.com]^[/url][url=http://friendica.com]Lesezeichen[/url] |
+ + |
[url=/posts/f16d77b0630f0134740c0cc47a0ea02a]Diaspora Beitrag mit GUID[/url] | +Diaspora Beitrag mit GUID | +
#Friendica | +#Friendica | +
@Erwähnung | +@Erwähnung | +
acct:account@friendica.host.com (WebFinger) | +acct:account@friendica.host.com | +
[mail]user@mail.example.com[/mail] | +user@mail.example.com | +
[mail=user@mail.example.com]Eine E-Mail senden[/mail] | +Eine E-Mail senden | +
BBCode | +Ergebnis | +
---|---|
[p]Ein Absatz mit Text[/p] | +Ein Absatz mit Text |
+
Eingebetteter [code]Programmcode[/code] im Text | +Eingebetteter |
+
[code]Programmcode über mehrere Zeilen[/code] |
+ Programmcode
+über
+mehrere
+Zeilen |
+
[code=php]function text_highlight($s,$lang)[/code] | +
|
+
[quote]Zitat[/quote] | +Zitat |
+
[quote=Autor]Autor? Ich? Nein, niemals...[/quote] | +Autor hat geschrieben:Autor? Ich? Nein, niemals... |
+
[center]zentrierter Text[/center] | +zentrierter Text |
+
Du solltest nicht weiter lesen, wenn du das Ende des Films nicht vorher erfahren willst. [spoiler]Es gibt ein Happy End.[/spoiler] | +
+
+ Du solltest nicht weiter lesen, wenn du das Ende des Films nicht vorher erfahren willst.
+ + Zum öffnen/schließen klicken + +
+ |
+
[spoiler=Autor]Spoiler Alarm[/spoiler] | +
+
+ Autor hat geschrieben
+ + Zum öffnen/schließen klicken + +
+ |
+
[hr] (horizontale Linie) | +
BBCode | +Ergebnis | +
---|---|
[h1]Titel 1[/h1] | +Titel 1 |
+
[h2]Titel 2[/h2] | +Titel 2 |
+
[h3]Titel 3[/h3] | +Titel 3 |
+
[h4]Titel 4[/h4] | +Titel 4 |
+
[h5]Titel 5[/h5] | +Titel 5 |
+
[h6]Titel 6[/h6] | +Titel 6 |
+
BBCode | +Ergebnis | +|||||||||
---|---|---|---|---|---|---|---|---|---|---|
[table] + [tr] + [th]Kopfzeile 1[/th] + [th]Kopfzeile 2[/th] + [th]Kopfzeile 2[/th] + [/tr] + [tr] + [td]Zelle 1[/td] + [td]Zelle 2[/td] + [td]Zelle 3[/td] + [/tr] + [tr] + [td]Zelle 4[/td] + [td]Zelle 5[/td] + [td]Zelle 6[/td] + [/tr] +[/table] |
+
+
|
+|||||||||
[table border=0] | +
+
|
+|||||||||
[table border=1] | +
+
|
+
BBCode | +Ergebnis | +
---|---|
[ul] + [li] Erstes Listenelement + [li] Zweites Listenelement +[/ul] +[list] + [*] Erstes Listenelement + [*] Zweites Listenelement +[/list] |
+
+
|
+
[ol] + [*] Erstes Listenelement + [*] Zweites Listenelement +[/ol] +[list=1] + [*] Erstes Listenelement + [*] Zweites Listenelement +[/list] |
+
+
|
+
[list=] + [*] Erstes Listenelement + [*] Zweites Listenelement +[/list] |
+
+
|
+
[list=i] + [*] Erstes Listenelement + [*] Zweites Listenelement +[/list] |
+
+
|
+
[list=I] + [*] Erstes Listenelement + [*] Zweites Listenelement +[/list] |
+
+
|
+
[list=a] + [*] Erstes Listenelement + [*] Zweites Listenelement +[/list] |
+
+
|
+
[list=A] + [*] Erstes Listenelement + [*] Zweites Listenelement +[/list] |
+
+
|
+
BBCode | +Ergebnis | +
---|---|
[video]url[/video] | +Wobei die *url* eine URL von youtube, vimeo, soundcloud oder einer anderen Plattform sein kann, die die opengraph Spezifikationen unterstützt. | +
[video]URL der Videodatei[/video] +[audio]URL der Musikdatei[/audio] | +Die komplette URL einer ogg/ogv/oga/ogm/webm/mp4/mp3 Datei angeben, diese wird dann mit einem HTML5-Player angezeigt. | +
[youtube]Youtube URL[/youtube] | +Youtube Video mittels OEmbed anzeigen. Kann u.U, den Player nicht einbetten. | +
[youtube]Youtube video ID[/youtube] | +Youtube-Player im iframe einbinden. | +
[vimeo]Vimeo URL[/vimeo] | +Vimeo Video mittels OEmbed anzeigen. Kann u.U, den Player nicht einbetten. | +
[vimeo]Vimeo video ID[/vimeo] | +Vimeo-Player im iframe einbinden. | +
[embed]URL[/embed] | +OEmbed rich content einbetten. | +
[iframe]URL[/iframe] | +General embed, iframe size is limited by the theme size for video players. | +
[url]*url*[/url] | +Wenn *url* die OEmbed- oder Opengraph-Spezifikationen unterstützt, wird das Objekt eingebettet (z.B. Dokumente von scribd). + Ansonsten wird der Titel der Seite mit der URL verlinkt. | +
BBCode | +Ergebnis | +
---|---|
[map]Adresse[/map] | +Bindet eine Karte ein, auf der die angegebene Adresse zentriert ist. | +
[map=lat,long] | +Bindet eine Karte ein, die auf die angegebenen Koordinaten zentriert ist. | +
[map] | +Bindet eine Karte ein, die auf die Position des Beitrags zentriert ist. | +
BBCode | +Ergebnis | +
---|---|
[abstract]Unglaublich interessant! Muss man gesehen haben! Unbedingt dem Link folgen![/abstract] +Ich möchte euch eine unglaublich langweilige Geschichte erzählen, die ihr sicherlich niemals hören wolltet. |
+ Auf Twitter würde folgender Text verlffentlicht werden Unglaublich interessant! Muss man gesehen haben! Unbedingt dem Link folgen!+Wohingegen auf Friendica folgendes stehen würde Ich möchte euch eine unglaublich langweilige Geschichte erzählen, die ihr sicherlich niemals hören wolltet. |
+
BBCode | +Ergebnis | +
---|---|
+[abstract]Hey Leute, hier sind meines neuesten Bilder![/abstract] +[abstract=twit]Hallo liebe Twitter Follower. Wollt ihr meine neuesten Bilder sehen?[/abstract] +[abstract=apdn]Moin liebe Follower auf ADN. Ich habe einige neue Bilder gemacht, die ich euch gerne zeigen will.[/abstract] +Heute war ich im Wald unterwegs und habe einige wirklich schöne Bilder gemacht... |
+ Für Twitter und App.net wird Friendica in diesem Fall die speziell definierten Zusammenfassungen Verwenden. Für andere Netzwerke (wie z.B. bei der Verwendung des GNU Social Konnektors zum Veröffentlichen auf deinen GNU Social Account) würde die allgemeine Zusammenfassung verwenden. | +
BBCode | +Ergebnis | +
---|---|
+[abstract]Dieser Tage hatte ich eine ungewöhnliche Begegnung...[/abstract] +[abstract=goog]Hey liebe Google+ Follower. Habt ich schon meinen neuesten Blog-Beitrag gelesen?[/abstract] +[abstract=face]Hallo liebe Facebook Freunde. Letztens ist mir etwas wirklich schönes paßiert.[/abstract] +Als ich die Bilder im Wald aufgenommen habe, hatte ich eine wirklich ungewöhnliche Begegnung... |
+ Auf Google und Facebook würde nun die entsprechende Zusammenfassung verbreitet. Für andere Netzwerke würde die allgemeine Zusammenfassung verwendet werden. + Auf Friendica wird weiterhin keine Zusammenfassung angezeigt. |
+
BBCode | +Ergebnis | +
---|---|
Wenn du verhindern möchtest, daß der BBCode in einer Nachricht interpretiert wird, kannst du die [noparse], [nobb] oder [pre] Tag verwenden: +
|
+ [b]fett[/b] | +
[nosmile] kann verwendet werden um für einen Beitrag das umsetzen von Smilies zu verhindern. + + [nosmile] ;-) :-O + |
+ ;-) :-O | +
Benutzerdefinierte Inline-Styles + +[style=text-shadow: 0 0 4px #CC0000;]Du kannst alle CSS-Eigenschaften eines Blocks ändern-[/style] |
+ Du kannst alle CSS-Eigenschaften eines Blocks ändern- | +
Benutzerdefinierte CSS Klassen + +[class=custom]Wenn die vergebene Klasse in den CSS Anweisungen existiert, wird sie angewandt.[/class] |
+ <span class="custom">Wenn die |
+
[b]fett[/b]: fett - -
[i]kursiv[/i]: kursiv - -
[u]unterstrichen[/u]: unterstrichen - -
[s]durchgestrichen[/s]:
[color=red]rot[/color]: rot - -
[url=http://www.friendica.com]Friendica[/url]: Friendica - -
[img]http://friendica.com/sites/default/files/friendika-32.png[/img]: - -
[size=xx-small]kleiner Text[/size]: kleiner Text - -
[size=xx-large]groß Text[/size]: großer Text - -
[size=20]exakte Textgröße[/size] (Textgröße kann jede Zahl sein, in Pixeln): exakte Größe - - - - - - - -Block Tags ------ - -
[code]Code[/code]- -
Code
-
-- -
[quote]Zitat[/quote]- -
Zitat- -
- -
[quote=Autor]Der Autor? Ich? Nein, nein, nein...[/quote]- -Autor hat geschrieben:
Der Autor? Ich? Nein, nein, nein...- -
- -
[center]zentrierter Text[/center]- -
- -
Wer überrascht werden möchte sollte nicht weiter lesen.[spoiler]Es gibt ein Happy End.[/spoiler]- -Wer überrascht werden möchte sollte nicht weiter lesen.
- -**Tabelle** -
[table border=1] - [tr] - [th]Tabellenzeile[/th] - [/tr] - [tr] - [td]haben Überschriften[/td] - [/tr] -[/table]- -
Tabellenzeile |
---|
haben Überschriften |
- -**Listen** - -
[list] - [*] Erstes Listenelement - [*] Zweites Listenelement -[/list]-
[ol] - [*] Erstes Listenelement - [*] Zweites Listenelement -[/ol]-
[list=1]: dezimal - -
[list=i]: römisch, Kleinbuchstaben - -
[list=I]: römisch, Großbuchstaben - -
[list=a]: alphabetisch, Kleinbuchstaben - -
[list=A]: alphabethisch, Großbuchstaben - - - - -Einbettung von Inhalten ------- - -Man kann viele Dinge, z.B. Video und Audio Dateine, in Nachrichten einbetten. - -
[video]url[/video]-
[audio]url[/audio]- -Wobei die *url* von youtube, vimeo, soundcloud oder einer anderen Seite stammen kann die die oembed oder opengraph Spezifikationen unterstützt. -Außerdem kann *url* die genaue url zu einer ogg Datei sein, die dann per HTML5 eingebunden wird. - -
[url]*url*[/url]- -Wenn *url* entweder oembed oder opengraph unterstützt wird das eingebettete Objekt (z.B. ein Dokument von scribd) eingebunden. -Der Titel der Seite mit einem Link zur *url* wird ebenfalls angezeigt. - -Um eine Karte in einen Beitrag einzubinden, muss das *openstreetmap* Addon aktiviert werden. Ist dies der Fall, kann mit - -
[map]Broadway 26, New York[/map]- -eine Karte von [OpenStreetmap](http://openstreetmap.org) eingebettet werden. Zur Identifikation des Ortes können entweder seine Koordinaten in der Form - -
[map=lat,long]- -oder eine Adresse in obiger Form verwendet werden. - -Zusammenfassung für längere Beiträge ------------------------------------- - -Wenn man seine Beiträge über mehrere Netzwerke verbreiten möchte, hat man häufig das Problem, dass diese Netzwerke z.B. eine Längenbeschränkung haben. -(Z.B. Twitter). - -Friendica benutzt zum Erzeugen eines Anreißtextes eine halbwegs intelligente Logik. -Es kann aber dennoch von Interesse sein, eine eigene Zusammenfassung zu erstellen, die nur auf dem Fremdnetzwerk dargestellt wird. -Dies geschieht mit dem [abstract]-Element. -Beispiel: - -
[abstract]Total spannend! Unbedingt diesen Link anklicken![/abstract] -Hier erzähle ich euch eine total langweilige Geschichte, die ihr noch -nie hören wolltet.- -Auf Twitter würde das "Total spannend! Unbedingt diesen Link anklicken!" stehen, auf Friendica würde nur der Text nach "Hier erzähle ..." erscheinen. - -Es ist sogar möglich, für einzelne Netzwerke eigene Zusammenfassungen zu erstellen: - -
-[abstract]Hallo Leute, hier meine neuesten Bilder![abstract] -[abstract=twit]Hallo Twitter-User, hier meine neuesten Bilder![abstract] -[abstract=apdn]Hallo App.net-User, hier meine neuesten Bilder![abstract] -Ich war heute wieder im Wald unterwegs und habe tolle Bilder geschossen ... -- -Für Twitter und App.net nimmt das System die entsprechenden Texte. -Bei anderen Netzwerken, bei denen der Inhalt gekürzt wird (z.B. beim "statusnet"-Connector, der für das Posten nach GNU Social verwendet wird) wird dann die Zusammenfassung unter [abstract] verwendet. - -Wenn man z.B. den "buffer"-Connector verwendet, um nach Facebook oder Google+ zu posten, kann man dieses Element ebenfalls verwenden, wenn man z.B. einen längeren Blogbeitrag erstellt hat, aber ihn nicht komplett in diese Netzwerke posten möchte. - -Netzwerke wie Facebook oder Google+ sind nicht in der Postinglänge beschränkt. -Aus diesem Grund greift nicht die [abstract]-Zusammenfassung. Stattdessen muss man das Netzwerk explizit angeben: - -
-[abstract]Ich habe neulich wieder etwas erlebt, was ich euch mitteilen möchte.[abstract] -[abstract=goog]Hallo meine Google+-Kreislinge. Ich habe neulich wieder -etwas erlebt, was ich euch mitteilen möchte.[abstract] -[abstract=face]Hallo Facebook-Freunde! Ich habe neulich wieder etwas -erlebt, was ich euch mitteilen möchte.[abstract] -Beim Bildermachen im Wald habe ich neulich eine interessante Person -getroffen ...- -Das [abstract]-Element greift nicht bei der nativen OStatus-Verbindung oder bei Connectoren, die den HTML-Text posten wie z.B. die Connectoren zu Tumblr, Wordpress oder Pump.io. - -Spezielle Tags -------- - -Wenn Du über BBCode Tags in einer Nachricht schreiben möchtest, kannst Du [noparse], [nobb] oder [pre] verwenden um den BBCode Tags vor der Evaluierung zu schützen: - -
[noparse][b]fett[/b][/noparse]: [b]fett[/b] diff --git a/doc/de/Bugs-and-Issues.md b/doc/de/Bugs-and-Issues.md index 1323b4b9d3..4db1bd6aa3 100644 --- a/doc/de/Bugs-and-Issues.md +++ b/doc/de/Bugs-and-Issues.md @@ -6,7 +6,7 @@ Bugs und Probleme Du solltest jeden Bug und jedes Problem, den/das Du findest, zunächst dem Administrator (oder gegebenenfalls der Support-Seite) Deines Servers melden, statt auf der allgemeinen Bug-Seite. Das erleichtert den Entwicklern ihre Arbeit (z. B. neue Features zu entwickeln), da sie sich nicht mit Fehlern beschäftigen müssen, mit denen sie nichts zu tun haben. -Wenn Du technisch versiert bist oder Dein Knoten keine Support-Seite hat, dann kannst Du den Bug Tracker nutzen. +Wenn Du technisch versiert bist oder Dein Knoten keine Support-Seite hat, dann kannst Du den Bug Tracker nutzen. Bitte durchsuche zunächst die Seite, ob es bereits einen offenen Bug gibt, der Deiner Anfrage entspricht. Liefere so viele Informationen wie möglich zu dem Bug. diff --git a/doc/de/Home.md b/doc/de/Home.md index 365e18cb3d..e3017c0c60 100644 --- a/doc/de/Home.md +++ b/doc/de/Home.md @@ -20,38 +20,37 @@ Friendica - Dokumentation und Ressourcen * [Community-Foren](help/Forums) * [Chats](help/Chats) * Weiterführende Informationen - * [Performance verbessern](help/Improve-Performance) * [Account umziehen](help/Move-Account) * [Account löschen](help/Remove-Account) * [Bugs und Probleme](help/Bugs-and-Issues) * [Häufig gestellte Fragen (FAQ)](help/FAQ) -**Technische Dokumentation** +**Dokumentation für Administratoren** * [Installation](help/Install) -* [Konfigurationen](help/Settings) +* [Konfigurationen & Admin-Panel](help/Settings) * [Plugins](help/Plugins) * [Konnektoren (Connectors) installieren (Twitter/GNU Social)](help/Installing-Connectors) * [Installation eines ejabberd Servers (XMPP-Chat) mit synchronisierten Anmeldedaten](help/install-ejabberd) (EN) -* [Nachrichtenfluss](help/Message-Flow) * [Betreibe deine Seite mit einem SSL-Zertifikat](help/SSL) -* [Entwickler](help/Developers) -* [Twitter/GNU Social API Functions](help/api) (EN) -* [Translation of Friendica](help/translations) (EN) * [Konfigurationswerte, die nur in der .htconfig.php gesetzt werden können](help/htconfig) (EN) +* [Performance verbessern](help/Improve-Performance) -**Entwickler Dokumentation** +**Dokumentation für Entwickler** -* [Where to get started?](help/Developers-Intro) +* [Entwickler](help/Developers) +* [Where to get started?](help/Developers-Intro) (EN) * [Help on Github](help/Github) * [Help on Vagrant](help/Vagrant) -* [How to translate Friendica](help/translations) +* [How to translate Friendica](help/translations) (EN) * [Bugs and Issues](help/Bugs-and-Issues) * [Plugin Development](help/Plugins) * [Theme Development](help/themes) * [Smarty 3 Templates](help/smarty3-templates) +* [Protokoll Dokumentation](help/Protocol) (EN) * [Datenbank-Schema](help/database) * [Code-Referenz (mit doxygen generiert - setzt Cookies)](doc/html/) +* [Twitter/GNU Social API Functions](help/api) (EN) **Externe Ressourcen** diff --git a/doc/de/Message-Flow.md b/doc/de/Message-Flow.md index 0694db1344..3d4c912ccf 100644 --- a/doc/de/Message-Flow.md +++ b/doc/de/Message-Flow.md @@ -6,7 +6,7 @@ Friendica Nachrichtenfluss Diese Seite soll einige Infos darüber dokumentieren, wie Nachrichten innerhalb von Friendica von einer Person zur anderen übertragen werden. Es gibt verschiedene Pfade, die verschiedene Protokolle und Nachrichtenformate nutzen. -Diejenigen, die den Nachrichtenfluss genauer verstehen wollen, sollten sich mindestens mit dem DFRN-Protokoll (http://dfrn.org/dfrn.pdf) und den Elementen zur Nachrichtenverarbeitung des OStatus Stack informieren (salmon und Pubsubhubbub). +Diejenigen, die den Nachrichtenfluss genauer verstehen wollen, sollten sich mindestens mit dem DFRN-Protokoll ([Dokument mit den DFRN Spezifikationen](https://github.com/friendica/friendica/blob/master/spec/dfrn2.pdf)) und den Elementen zur Nachrichtenverarbeitung des OStatus Stack informieren (salmon und Pubsubhubbub). Der Großteil der Nachrichtenverarbeitung nutzt die Datei include/items.php, welche Funktionen für verschiedene Feed-bezogene Import-/Exportaktivitäten liefert. @@ -24,7 +24,7 @@ PuSh-Feeds (pubsubhubbub) kommen via mod/pubsub.php an. DFRN-poll Feed-Imports kommen via include/poller.php als geplanter Task an, das implementiert die lokale Bearbeitung (local side) des DFRN-Protokolls. -Szenario #1. Bob schreibt eine öffentliche Statusnachricht +### Szenario #1. Bob schreibt eine öffentliche Statusnachricht Dies ist eine öffentliche Nachricht ohne begrenzte Nutzerfreigabe, so dass keine private Übertragung notwendig ist. Es gibt zwei Wege, die genutzt werden können - als bbcode an DFRN-Clients oder als durch den Server konvertierten HTML-Code (mit PuSH; pubsubhubbub). @@ -33,13 +33,13 @@ Sie fallen zurück auf eine tägliche Abfrage, wenn der Hub Übertragungsschwier Wenn kein spezifizierter Hub oder Hubs ausgewählt sind, werden DFRN-Clients in einer pro Kontakt konfigurierbaren Rate mit bis zu 5-Minuten-Intervallen abfragen. Feeds, die via DFRN-Poll abgerufen werden, sind bbcode und können auch private Unterhaltungen enthalten, die vom Poller auf ihre Zugriffsrechte hin geprüft werden. -Szenario #2. Jack antwortet auf Bobs öffentliche Nachricht. Jack ist im Friendica/DFRN-Netzwerk. +### Szenario #2. Jack antwortet auf Bobs öffentliche Nachricht. Jack ist im Friendica/DFRN-Netzwerk. Jack nutzt dfrn-notify, um eine direkte Antwort an Bob zu schicken. Bob erstellt dann einen Feed der Unterhaltung und sendet diesen an jeden, der an der Unterhaltung beteiligt ist und dfrn-notify nutzt. Die PuSH-Hubs werden darüber informiert, dass neuer Inhalt verfügbar ist. Der/die Hub/s erhalten dann die neuesten Feeds und übertragen diese an alle Hub-Teilnehmer (die auch zu verschiedenen Netzwerken gehören können). -Szenario #3. Mary antwortet auf Bobs öffentliche Nachricht. Mary ist im Friendica/DFRN-Netzwerk. +### Szenario #3. Mary antwortet auf Bobs öffentliche Nachricht. Mary ist im Friendica/DFRN-Netzwerk. Mary nutzt dfrn-notify, um eine direkte Antwort an Bob zu schicken. Bob erstellt dann einen Feed der Unterhaltung und sendet diesen an jeden, der an der Unterhaltung beteiligt ist (mit Ausnahme von Bob selbst; die Unterhaltung wird nun an Jack und Mary geschickt). @@ -47,14 +47,14 @@ Die Nachrichten werden mit dfrn-notify übertragen. PuSH-Hubs werden darüber informiert, dass neuer Inhalt verfügbar ist. Der/die Hub/s erhalten dann die neuesten Feeds und übertragen sie an alle Hub-Teilnehmer (die auch zu verschiedenen Netzwerken gehören können). -Szenario #4. William antwortet auf Bobs öffentliche Nachricht. William ist in einem OStatus-Netzwerk. +### Szenario #4. William antwortet auf Bobs öffentliche Nachricht. William ist in einem OStatus-Netzwerk. William nutzt salmon, um Bob über seine Antwort zu benachrichtigen. Der Inhalt ist HTML-Code, der in das Salmon Magic Envelope eingebettet ist. Bob erstellt dann einen Feed der Unterhaltung und sendet es an alle Friendica-Nutzer, die an der Unterhaltung beteiligt sind und dfrn-notify nutzen (mit Ausnahme von William selbst; die Unterhaltung wird an Jack und Mary weitergeleitet). PuSH-Hubs werden darüber informiert, dass neuer Inhalt verfügbar ist. Der/die Hub/s erhalten dann die neuesten Feeds und übertragen sie an alle Hub-Teilnehmer (die auch zu verschiedenen Netzwerken gehören können). -Szenario #5. Bob schreibt eine private Nachricht an Mary und Jack. +### Szenario #5. Bob schreibt eine private Nachricht an Mary und Jack. Die Nachricht wird sofort an Mary und Jack mit Hilfe von dfrn_notify geschickt. Öffentliche Hubs werden nicht benachrichtigt. diff --git a/doc/de/Plugins.md b/doc/de/Plugins.md index 40be4a0695..8b1c71177d 100644 --- a/doc/de/Plugins.md +++ b/doc/de/Plugins.md @@ -40,7 +40,7 @@ Argumente Deine Hook-Callback-Funktion wird mit mindestens einem und bis zu zwei Argumenten aufgerufen - function myhook_function(&$a, &$b) { + function myhook_function(App $a, &$b) { } @@ -67,9 +67,9 @@ So würde http://example.com/plugin/arg1/arg2 nach einem Modul "plugin" suchen u $a->argc = 3 $a->argv = array(0 => 'plugin', 1 => 'arg1', 2 => 'arg2'); -Deine Modulfunktionen umfassen oft die Funktion plugin_name_content(&$a), welche den Seiteninhalt definiert und zurückgibt. -Sie können auch plugin_name_post(&$a) umfassen, welches vor der content-Funktion aufgerufen wird und normalerweise die Resultate der POST-Formulare handhabt. -Du kannst ebenso plugin_name_init(&$a) nutzen, was oft frühzeitig aufgerufen wird und das Modul initialisert. +Deine Modulfunktionen umfassen oft die Funktion plugin_name_content(App $a), welche den Seiteninhalt definiert und zurückgibt. +Sie können auch plugin_name_post(App $a) umfassen, welches vor der content-Funktion aufgerufen wird und normalerweise die Resultate der POST-Formulare handhabt. +Du kannst ebenso plugin_name_init(App $a) nutzen, was oft frühzeitig aufgerufen wird und das Modul initialisert. Derzeitige Hooks @@ -311,7 +311,7 @@ mod/photos.php: call_hooks('photo_post_end',intval($item_id)); mod/photos.php: call_hooks('photo_upload_form',$ret); -mod/friendica.php: call_hooks('about_hook', $o); +mod/friendica.php: call_hooks('about_hook', $o); mod/editpost.php: call_hooks('jot_tool', $jotplugins); diff --git a/doc/de/SSL.md b/doc/de/SSL.md index e9deb21b7b..d1929120a4 100644 --- a/doc/de/SSL.md +++ b/doc/de/SSL.md @@ -5,7 +5,7 @@ Friendica mit SSL nutzen Disclaimer --- -**Dieses Dokument wurde im November 2015 aktualisiert. +**Dieses Dokument wurde im November 2016 aktualisiert. SSL-Verschlüsselung ist sicherheitskritisch. Das bedeutet, dass sich die empfohlenen Einstellungen schnell verändern. Halte deine Installation auf dem aktuellen Stand und verlasse dich nicht darauf, dass dieses Dokument genau so schnell aktualisiert wird, wie sich Technologien verändern!** @@ -45,55 +45,15 @@ Sie installieren es für dich oder haben in der Weboberfläche eine einfache Upl Um Geld zu sparen, kann es sich lohnen, dort auch nachzufragen, ob sie ein anderes Zertifikat, das du selbst beschaffst, für dich installieren würden. Wenn ja, dann lies weiter. -Ein kostenloses StartSSL-Zertifikat besorgen ---- - -StartSSL ist eine Zertifizierungsstelle, die kostenlose Zertifikate ausstellt. -Sie sind für ein Jahr gültig und genügen für unsere Zwecke. - -### Schritt 1: Client-Zertifikat erstellen - -Wenn du dich erstmalig bei StartSSL anmeldest, erhältst du ein Zertifikat, das in deinem Browser installiert wird. -Du brauchst es, um dich bei StartSSL einzuloggen, auch wenn du später wiederkommst. -Dieses Client-Zertifikat hat nichts mit dem SSL-Zertifikat für deinen Server zu tun. - -### Schritt 2: Email-Adresse und Domain validieren - -Um fortzufahren musst du beweisen, dass du die Email-Adresse, die du angegeben hast, und die Domain, für die du das Zertifikat möchtest, besitzt. -Gehe in den "Validation wizard" und fordere einen Bestätigungslink per Mail an. -Dasselbe machst du auch für die Validierung der Domain. - -### Schritt 3: Das Zertifikat bestellen - -Gehe in den "Certificate wizard". -Wähle das Target Webserver. -Bei der ersten Abfrage der Domain gibst du deine Hauptdomain an. -Im nächsten Schritt kannst du eine Subdomain hinzufügen. -Ein Beispiel: Wenn die Adresse der Friendica-Instanz friendica.beispiel.net lautet, gibst du zuerst beispiel.net an und danach friendica. - -Wenn du weißt, wie man einen openssl-Schlüssel und einen Certificate Signing Request (CSR) erstellt, tu das. -Kopiere den CSR in den Browser, um ihn von StartSSL signiert zu bekommen. - -Wenn du nicht weißt, wie man Schlüssel und CSR erzeugt, nimm das Angebot von StartSSL an, beides für dich zu generieren. -Das bedeutet: StartSSL hat den Schlüssel zu deiner SSL-Verschlüsselung, aber das ist immer noch besser als gar kein Zertifikat. -Lade dein Zertifikat von der Website herunter. -(Oder im zweiten Fall: Lade Zertifikat und Schlüssel herunter.) - -Um dein Zertifikat auf einem Webserver zu installieren, brauchst du noch ein oder zwei andere Dateien: sub.class1.server.ca.pem und ca.pem, auch von StartSSL. -Gehe in die Rubrik "Tool box" und lade "Class 1 Intermediate Server CA" und "StartCom Root CA (PEM encoded)" herunter. - -Wenn du dein Zertifikat zu deinem Hosting-Provider schicken möchtest, brauchen Sie mindestens Zertifikat und Schlüssel. -Schick zur Sicherheit alle vier Dateien hin. -**Du solltest sie auf einem verschlüsselten Weg hinschicken!** - -Wenn du deinen eigenen Server betreibst, lade die Dateien hoch und besuche das Mozilla-Wiki (Link unten). Let's encrypt --- Wenn du einen eigenen Server betreibst und den Nameserver kontrollierst, könnte auch die Initiative "Let's encrypt" interessant für dich werden. -Momentan ist deren Angebot noch nicht fertig. -Auf der [Website](https://letsencrypt.org/) kannst du dich über den Stand informieren. +Sie bietet nicht nur freie SSL Zertifikate sondern auch einen automatisierten Prozess zum Erneuern der Zertifikate. +Um letsencrypt Zertifikate verwenden zu können, musst du dir einen Client auf deinem Server installieren. +Eine Anleitung zum offiziellen Client findet du [hier](https://certbot.eff.org/). +Falls du dir andere Clients anschauen willst, kannst du einen Blick in diese [Liste von alternativen letsencrypt Clients](https://letsencrypt.org/docs/client-options/). Webserver-Einstellungen --- diff --git a/doc/de/Settings.md b/doc/de/Settings.md index 4ad9f39ba5..2b7e89a52c 100644 --- a/doc/de/Settings.md +++ b/doc/de/Settings.md @@ -1,231 +1,76 @@ -Konfigurationen -============== +# Settings * [Zur Startseite der Hilfe](help) -Hier findest du einige eingebaute Features, welche kein graphisches Interface haben oder nicht dokumentiert sind. -Konfigurationseinstellungen sind in der Datei ".htconfig.php" gespeichert. -Bearbeite diese Datei, indem du sie z.B. mit einem Texteditor öffnest. -Verschiedene Systemeinstellungen sind bereits in dieser Datei dokumentiert und werden hier nicht weiter erklärt. +Wenn du der Administrator einer Friendica Instanz bist, hast du Zugriff auf das so genannte **Admin Panel** in dem du die Friendica Instanz konfigurieren kannst, -**Tastaturbefehle** +Auf der Startseite des Admin Panels werden die Informationen zu der Instanz zusammengefasst. +Diese Informationen beinhalten die Anzahl der Nachrichten, die sich aktuell in den Warteschlangen befinden. +Hierbei ist die erste Zahl die Zahl der Nachrichten die gerade aktiv verteilt werden. +Diese Zahl sollte sich relativ schnell sinken. +Die zweite Zahl gibt die Anzahl von Nachrichten an, die nicht zugestellt werden konnten. +Die Zustellung wird zu einem späteren Zeitpunkt noch einmal versucht. +Unter dem Punkt "Warteschlange Inspizieren" kannst du einen schnellen Blick auf die zweite Warteschlange werfen. +Solltest du für die Hintergrundprozesse die Worker aktiviert haben, wird eine dritte Zahl angezeigt. +Diese repräsentiert die Anzahl der Aufgaben, die die Worker noch vor sich haben. +Die Aufgaben der Worker sind priorisiert und werden anhand dieser Prioritäten abgearbeitet. -Friendica erfasst die folgenden Tastaturbefehle: +Des weiteren findest du eine Übersicht über die Accounts auf dem Friendica Knoten, die unter dem Punkt "Nutzer" moderiert werden können. +Sowie eine Liste der derzeit aktivierten Addons. +Diese Liste ist verlinkt, so dass du schnellen Zugriff auf die Informationsseiten der einzelnen Addons hast. +Abschließend findest du auf der Startseite des Admin Panels die installierte Version von Friendica. +Wenn du in Kontakt mit den Entwicklern trittst und Probleme oder Fehler zu schildern, gib diese Version bitte immer mit an. -* [Pause] - Pausiert die Update-Aktivität via "Ajax". Das ist ein Prozess, der Updates durchführt, ohne die Seite neu zu laden. Du kannst diesen Prozess pausieren, um deine Netzwerkauslastung zu reduzieren und/oder um es in der Javascript-Programmierung zum Debuggen zu nutzen. Ein Pausenzeichen erscheint unten links im Fenster. Klicke die [Pause]-Taste ein weiteres Mal, um die Pause zu beenden. +Die Unterabschnitte des Admin Panels kannst du in der Seitenleiste auswählen. -**Geburtstagsbenachrichtigung** +## Seite -Geburtstage erscheinen auf deiner Startseite für alle Freunde, die in den nächsten 6 Tagen Geburtstag haben. -Um deinen Geburtstag für alle sichtbar zu machen, musst du deinen Geburtstag (zumindest Tag und Monat) in dein Standardprofil eintragen. -Es ist nicht notwendig, das Jahr einzutragen. +In diesem Bereich des Admin Panels findest du die Hauptkonfiguration deiner Friendica Instanz. +Er ist in mehrere Unterabschnitte aufgeteilt, wobei die Grundeinstellungen oben auf der Seite zu finden sind. -**Konfigurationseinstellungen** +Da die meisten Konfigurationsoptionen einen Hilfstext im Admin Panel haben, kann und will dieser Artikel nicht alle Einstellungen abdecken. +### Grundeinstellungen -**Sprache** - -Systemeinstellung - -Bitte schau dir die Datei util/README an, um Informationen zur Erstellung einer Übersetzung zu erhalten. - -Konfiguriere: -``` -$a->config['system']['language'] = 'name'; -``` - - -**System-Thema (Design)** - -Systemeinstellung - -Wähle ein Thema als Standardsystemdesign (welches vom Nutzer überschrieben werden kann). Das Standarddesign ist "default". - -Konfiguriere: -``` -$a->config['system']['theme'] = 'theme-name'; -``` - - -**Verifiziere SSL-Zertifikate** - -Sicherheitseinstellungen - -Standardmäßig erlaubt Friendica SSL-Kommunikation von Seiten, die "selbstunterzeichnete" SSL-Zertifikate nutzen. -Um eine weitreichende Kompatibilität mit anderen Netzwerken und Browsern zu gewährleisten, empfehlen wir, selbstunterzeichnete Zertifikate **nicht** zu nutzen. -Aber wir halten dich nicht davon ab, solche zu nutzen. SSL verschlüsselt alle Daten zwischen den Webseiten (und für deinen Browser), was dir eine komplett verschlüsselte Kommunikation erlaubt. -Auch schützt es deine Login-Daten vor Datendiebstahl. Selbstunterzeichnete Zertifikate können kostenlos erstellt werden. -Diese Zertifikate können allerdings Opfer eines sogenannten ["man-in-the-middle"-Angriffs](http://de.wikipedia.org/wiki/Man-in-the-middle-Angriff) werden, und sind daher weniger bevorzugt. -Wenn du es wünscht, kannst du eine strikte Zertifikatabfrage einstellen. -Das führt dazu, dass du keinerlei Verbindung zu einer selbstunterzeichneten SSL-Seite erstellen kannst - -Konfiguriere: -``` -$a->config['system']['verifyssl'] = true; -``` - - -**Erlaubte Freunde-Domains** - -Kooperationen/Gemeinschaften/Bildung Erweiterung - -Kommagetrennte Liste von Domains, welche eine Freundschaft mit dieser Seite eingehen dürfen. -Wildcards werden akzeptiert (Wildcard-Unterstützung unter Windows benötigt PHP5.3) Standardmäßig sind alle gültigen Domains erlaubt. - -Konfiguriere: -``` -$a->config['system']['allowed_sites'] = "sitea.com, *siteb.com"; -``` - - -**Erlaubte Email-Domains** - -Kooperationen/Gemeinschaften/Bildung Erweiterung - -Kommagetrennte Liste von Domains, welche bei der Registrierung als Part der Email-Adresse erlaubt sind. -Das grenzt Leute aus, die nicht Teil der Gruppe oder Organisation sind. -Wildcards werden akzeptiert (Wildcard-Unterstützung unter Windows benötigt PHP5.3) Standardmäßig sind alle gültigen Email-Adressen erlaubt. - -Konfiguriere: -``` -$a->config['system']['allowed_email'] = "sitea.com, *siteb.com"; -``` - -**Öffentlichkeit blockieren** - -Kooperationen/Gemeinschaften/Bildung Erweiterung - -Setze diese Einstellung auf "true" und sperre den öffentlichen Zugriff auf alle Seiten, solange man nicht eingeloggt ist. -Das blockiert die Ansicht von Profilen, Freunden, Fotos, vom Verzeichnis und den Suchseiten. -Ein Nebeneffekt ist, dass Einträge dieser Seite nicht im globalen Verzeichnis erscheinen. -Wir empfehlen, speziell diese Einstellung auszuschalten (die Einstellung ist an anderer Stelle auf dieser Seite erklärt). -Beachte: das ist speziell für Seiten, die beabsichtigen, von anderen Friendica-Netzwerken abgeschottet zu sein. -Unautorisierte Personen haben ebenfalls nicht die Möglichkeit, Freundschaftsanfragen von Seitennutzern zu beantworten. -Die Standardeinstellung steht auf "false". -Verfügbar in Version 2.2 und höher. - -Konfiguriere: -``` -$a->config['system']['block_public'] = true; -``` - - -**Veröffentlichung erzwingen** - -Kooperationen/Gemeinschaften/Bildung Erweiterung - -Standardmäßig können Nutzer selbst auswählen, ob ihr Profil im Seitenverzeichnis erscheint. -Diese Einstellung zwingt alle Nutzer dazu, im Verzeichnis zu erscheinen. -Diese Einstellung kann vom Nutzer nicht deaktiviert werden. Die Standardeinstellung steht auf "false". - -Konfiguriere: -``` -$a->config['system']['publish_all'] = true; -``` - - -**Globales Verzeichnis** - -Kooperationen/Gemeinschaften/Bildung Erweiterung - -Mit diesem Befehl wird die URL eingestellt, die zum Update des globalen Verzeichnisses genutzt wird. -Dieser Befehl ist in der Standardkonfiguration enthalten. -Der nichtdokumentierte Teil dieser Einstellung ist, dass das globale Verzeichnis gar nicht verfügbar ist, wenn diese Einstellung nicht gesetzt wird. -Dies erlaubt eine private Kommunikation, die komplett vom globalen Verzeichnis isoliert ist. - -Konfiguriere: -``` -$a->config['system']['directory'] = 'http://dir.friendi.ca'; -``` - - -**Proxy Konfigurationseinstellung** - -Wenn deine Seite eine Proxy-Einstellung nutzt, musst du diese Einstellungen vornehmen, um mit anderen Seiten im Internet zu kommunizieren. - -Konfiguriere: -``` -$a->config['system']['proxy'] = "http://proxyserver.domain:port"; -$a->config['system']['proxyuser'] = "username:password"; -``` - - -**Netzwerk-Timeout** - -Legt fest, wie lange das Netzwerk warten soll, bevor ein Timeout eintritt. -Der Wert wird in Sekunden angegeben. Standardmäßig ist 60 eingestellt; 0 steht für "unbegrenzt" (nicht empfohlen). - -Konfiguriere: - -``` -$a->config['system']['curl_timeout'] = 60; -``` - - -**Banner/Logo** +#### Banner/Logo Hiermit legst du das Banner der Seite fest. Standardmäßig ist das Friendica-Logo und der Name festgelegt. Du kannst hierfür HTML/CSS nutzen, um den Inhalt zu gestalten und/oder die Position zu ändern, wenn es nicht bereits voreingestellt ist. -Konfiguriere: +#### Systensprache -``` -$a->config['system']['banner'] = 'Meine tolle Webseite'; -``` +Diese Einstellung legt die Standardsprache der Instanz fest. +Sie wird verwendet, wenn es Friendica nicht gelingt die Spracheinstellungen des Besuchers zu erkennen oder diese nicht unterstützt wird. +Nutzer können diese Auswahl in den Einstellungen des Benutzerkontos überschreiben. +Die Friendica Gemeinschaft bietet einige Übersetzungen an, von denen einige mehr andere weniger komplett sind. +Mehr Informationen zum Übersetzungsprozess von Friendica findest du [auf dieser Seite](/help/translations) der Dokumentation. -**Maximale Bildgröße** +#### Systemweites Theme -Maximale Bild-Dateigröße in Byte. Standardmäßig ist 0 gesetzt, was bedeutet, dass kein Limit gesetzt ist. +Hier kann das Theme bestimmt werden, welches standardmäßig zum Anzeigen der Seite verwendet werden soll. +Nutzer können in ihren Einstellungen andere Themes wählen. +Derzeit ist das "duepunto zero" Theme das vorausgewählte Theme. -Konfiguriere: +Für mobile Geräte kannst du ein spezielles Theme wählen, wenn das Standardtheme ungeeignet für mobile Geräte sein sollte. +Das `vier` Theme z.B. unterstützt kleine Anzeigen und benötigt kein zusätzliches mobiles Theme. -``` -$a->config['system']['maximagesize'] = 1000000; -``` +### Registrierung - -**UTF-8 Reguläre Ausdrücke** - -Während der Registrierung werden die Namen daraufhin geprüft, ob sie reguläre UTF-8-Ausdrücke nutzen. -Hierfür wird PHP benötigt, um mit einer speziellen Einstellung kompiliert zu werden, die UTF-8-Ausdrücke benutzt. -Wenn du absolut keine Möglichkeit hast, Accounts zu registrieren, setze den Wert von "no_utf" auf "true". -Standardmäßig ist "false" eingestellt (das bedeutet, dass UTF-8-Ausdrücke unterstützt werden und funktionieren). - -Konfiguriere: - -``` -$a->config['system']['no_utf'] = true; -``` - - -**Prüfe vollständigen Namen** +#### Namen auf Vollständigkeit überprüfen Es kann vorkommen, dass viele Spammer versuchen, sich auf deiner Seite zu registrieren. In Testphasen haben wir festgestellt, dass diese automatischen Registrierungen das Feld "Vollständiger Name" oft nur mit Namen ausfüllen, die kein Leerzeichen beinhalten. Wenn du Leuten erlauben willst, sich nur mit einem Namen anzumelden, dann setze die Einstellung auf "true". Die Standardeinstellung ist auf "false" gesetzt. - -Konfiguriere: - -``` -$a->config['system']['no_regfullname'] = true; -``` - - -**OpenID** + +#### OpenID Unterstützung Standardmäßig wird OpenID für die Registrierung und für Logins genutzt. Wenn du nicht willst, dass OpenID-Strukturen für dein System übernommen werden, dann setze "no_openid" auf "true". Standardmäßig ist hier "false" gesetzt. -Konfiguriere: -``` -$a->config['system']['no_openid'] = true; -``` - - -**Multiple Registrierungen** +#### Unterbinde Mehrfachregistrierung Um mehrfache Seiten zu erstellen, muss sich eine Person mehrfach registrieren können. Deine Seiteneinstellung kann Registrierungen komplett blockieren oder an Bedingungen knüpfen. @@ -234,42 +79,246 @@ Hier ist weiterhin eine Bestätigung notwendig, wenn "REGISTER_APPROVE" ausgewä Wenn du die Erstellung weiterer Accounts blockieren willst, dann setze die Einstellung "block_extended_register" auf "true". Standardmäßig ist hier "false" gesetzt. -Konfiguriere: -``` -$a->config['system']['block_extended_register'] = true; -``` +### Datei hochladen +#### Maximale Bildgröße -**Entwicklereinstellungen** +Maximale Bild-Dateigröße in Byte. Standardmäßig ist 0 gesetzt, was bedeutet, dass kein Limit gesetzt ist. -Diese sind am nützlichsten, um Protokollprozesse zu debuggen oder andere Kommunikationsfehler einzugrenzen. +### Regeln -Konfiguriere: -``` -$a->config['system']['debugging'] = true; -$a->config['system']['logfile'] = 'logfile.out'; -$a->config['system']['loglevel'] = LOGGER_DEBUG; -``` -Erstellt detaillierte Debugging-Logfiles, die in der Datei "logfile.out" gespeichert werden (Datei muss auf dem Server mit Schreibrechten versehen sein). "LOGGER_DEBUG" zeigt eine Menge an Systeminformationen, enthält aber keine detaillierten Daten. -Du kannst ebenfalls "LOGGER_ALL" auswählen, allerdings empfehlen wir dieses nur, wenn ein spezifisches Problem eingegrenzt werden soll. -Andere Log-Level sind möglich, werden aber derzeit noch nicht genutzt. +#### URL des weltweiten Verzeichnisses +Mit diesem Befehl wird die URL eingestellt, die zum Update des globalen Verzeichnisses genutzt wird. +Dieser Befehl ist in der Standardkonfiguration enthalten. +Der nicht dokumentierte Teil dieser Einstellung ist, dass das globale Verzeichnis gar nicht verfügbar ist, wenn diese Einstellung nicht gesetzt wird. +Dies erlaubt eine private Kommunikation, die komplett vom globalen Verzeichnis isoliert ist. -**PHP-Fehler-Logging** +#### Erzwinge Veröffentlichung -Nutze die folgenden Einstellungen, um PHP-Fehler direkt in einer Datei zu erfassen. +Standardmäßig können Nutzer selbst auswählen, ob ihr Profil im Seitenverzeichnis erscheint. +Diese Einstellung zwingt alle Nutzer dazu, im Verzeichnis zu erscheinen. +Diese Einstellung kann vom Nutzer nicht deaktiviert werden. Die Standardeinstellung steht auf "false". -Konfiguriere: -``` -error_reporting(E_ERROR | E_WARNING | E_PARSE ); -ini_set('error_log','php.out'); -ini_set('log_errors','1'); -ini_set('display_errors', '0'); -``` +#### Öffentlichen Zugriff blockieren -Diese Befehle erfassen alle PHP-Fehler in der Datei "php.out" (Datei muss auf dem Server mit Schreibrechten versehen sein). -Nicht deklarierte Variablen werden manchmal mit einem Verweis versehen, weshalb wir empfehlen, "E_NOTICE" und "E_ALL" nicht zu nutzen. -Die Menge an Fehlern, die auf diesem Level gemeldet werden, ist komplett harmlos. -Bitte informiere die Entwickler über alle Fehler, die du in deinen Log-Dateien mit den oben genannten Einstellungen erhältst. -Sie weisen generell auf Fehler in, die bearbeitet werden müssen. -Wenn du eine leere (weiße) Seite erhältst, schau in die PHP-Log-Datei - dies deutet fast immer darauf hin, dass ein Fehler aufgetreten ist. +Aktiviere diese Einstellung um den öffentlichen Zugriff auf alle Seiten zu sperren, solange man nicht eingeloggt ist. +Das blockiert die Ansicht von Profilen, Freunden, Fotos, vom Verzeichnis und den Suchseiten. +Ein Nebeneffekt ist, dass Einträge dieser Seite nicht im globalen Verzeichnis erscheinen. +Wir empfehlen, speziell diese Einstellung auszuschalten (die Einstellung ist an anderer Stelle auf dieser Seite erklärt). +Beachte: das ist speziell für Seiten, die beabsichtigen, von anderen Friendica-Netzwerken abgeschottet zu sein. +Unautorisierte Personen haben ebenfalls nicht die Möglichkeit, Freundschaftsanfragen von Seitennutzern zu beantworten. +Die Standardeinstellung ist deaktiviert. +Verfügbar in Version 2.2 und höher. + +#### Erlaubte Domains für Kontakte + +Kommagetrennte Liste von Domains, welche eine Freundschaft mit dieser Seite eingehen dürfen. +Wildcards werden akzeptiert (Wildcard-Unterstützung unter Windows benötigt PHP5.3) Standardmäßig sind alle gültigen Domains erlaubt. + +Mit dieser Option kann man einfach geschlossene Netzwerke, z.B. im schulischen Bereich aufbauen, aus denen nicht mit dem Rest des Netzwerks kommuniziert werden soll. + +#### Erlaubte Domains für E-Mails + +Kommagetrennte Liste von Domains, welche bei der Registrierung als Part der Email-Adresse erlaubt sind. +Das grenzt Leute aus, die nicht Teil der Gruppe oder Organisation sind. +Wildcards werden akzeptiert (Wildcard-Unterstützung unter Windows benötigt PHP5.3) Standardmäßig sind alle gültigen Email-Adressen erlaubt. + +#### Nutzern erlauben das remote_self Flag zu setzen + +Webb du die Option `Nutzern erlauben das remote_self Flag zu setzen` aktivierst, können alle Nutzer Atom Feeds in den erweiterten Einstellungen des Kontakts als "Entferntes Konto" markieren. +Dadurch werden automatisch alle Beiträge dieser Feeds für diesen Nutzer gespiegelt und an die Kontakte bei Friendica verteilt. + +Als Administrator der Friendica Instanz kannst du diese Einstellungen ansonsten nur direkt in der Datenbank vornehmen. +Bevor du das tust solltest du sicherstellen, dass du ein Backup der Datenbank hast und genau weißt was die Änderungen an der Datenbank bewirken, die du vornehmen willst. + +### Erweitert + +#### Proxy Einstellungen + +Wenn deine Seite eine Proxy-Einstellung nutzt, musst du diese Einstellungen vornehmen, um mit anderen Seiten im Internet zu kommunizieren. + +#### Netzwerk Wartezeit + +Legt fest, wie lange das Netzwerk warten soll, bevor ein Timeout eintritt. +Der Wert wird in Sekunden angegeben. Standardmäßig ist 60 eingestellt; 0 steht für "unbegrenzt" (nicht empfohlen). + +#### UTF-8 Reguläre Ausdrücke + +Während der Registrierung werden die Namen daraufhin geprüft, ob sie reguläre UTF-8-Ausdrücke nutzen. +Hierfür wird PHP benötigt, um mit einer speziellen Einstellung kompiliert zu werden, die UTF-8-Ausdrücke benutzt. +Wenn du absolut keine Möglichkeit hast, Accounts zu registrieren, setze diesen Wert auf ja. + +#### SSL Überprüfen + +Standardmäßig erlaubt Friendica SSL-Kommunikation von Seiten, die "selbst unterzeichnete" SSL-Zertifikate nutzen. +Um eine weitreichende Kompatibilität mit anderen Netzwerken und Browsern zu gewährleisten, empfehlen wir, selbst unterzeichnete Zertifikate **nicht** zu nutzen. +Aber wir halten dich nicht davon ab, solche zu nutzen. SSL verschlüsselt alle Daten zwischen den Webseiten (und für deinen Browser), was dir eine komplett verschlüsselte Kommunikation erlaubt. +Auch schützt es deine Login-Daten vor Datendiebstahl. Selbst unterzeichnete Zertifikate können kostenlos erstellt werden. +Diese Zertifikate können allerdings Opfer eines sogenannten ["man-in-the-middle"-Angriffs](http://de.wikipedia.org/wiki/Man-in-the-middle-Angriff) werden, und sind daher weniger bevorzugt. +Wenn du es wünscht, kannst du eine strikte Zertifikatabfrage einstellen. +Das führt dazu, dass du keinerlei Verbindung zu einer selbst unterzeichneten SSL-Seite erstellen kannst + +### Automatisch ein Kontaktverzeichnis erstellen + +### Performance + +### Worker + +### Umsiedeln + +## Nutzer + +In diesem Abschnitt des Admin Panels kannst du die Nutzer deiner Friendica Instanz moderieren. + +Solltest du für **Registrierungsmethode** die Einstellung "Bedarf Zustimmung" gewählt haben, werden hier zu Beginn der Seite neue Registrationen aufgelistet. +Als Administrator kannst du hier die Registration akzeptieren oder ablehnen. + +Unter dem Abschnitt mit den Registrationen werden die aktuell auf der Instanz registrierten Nutzer aufgelistet. +Die Liste kann nach Namen, E-Mail Adresse, Datum der Registration, der letzten Anmeldung oder dem letzten Beitrag und dem Account Typ sortiert werden. +An dieser Stelle kannst du existierende Accounts vom Zugriff auf die Instanz blockieren, sie wieder frei geben oder Accounts endgültig löschen. + +Im letzten Bereich auf der Seite kannst du als Administrator neue Accounts anlegen. +Das Passwort für so eingerichtete Accounts werden per E-Mail an die Nutzer geschickt. + +## Plugins + +Dieser Bereich des Admin Panels dient der Auswahl und Konfiguration der Erweiterungen von Friendica. +Sie müssen in das `/addon` Verzeichnis kopiert werden. +Auf der Seite wird eine Liste der verfügbaren Erweiterungen angezeigt. +Neben den Namen der Erweiterungen wird ein Indikator angezeigt, der anzeigt ob das Addon gerade aktiviert ist oder nicht. + +Wenn du die Erweiterungen aktualisiert die du auf deiner Friendica Instanz nutzt könnte es sein, dass sie neu geladen werden müssen, damit die Änderungen aktiviert werden. +Um diesen Prozess zu vereinfachen gibt es am Anfang der Seite einen Button um alle aktiven Plugins neu zu laden. + +## Themen + +Der Bereich zur Kontrolle der auf der Friendica Instanz verfügbaren Themen funktioniert analog zum Plugins Bereich. +Jedes Theme hat eine extra Seite auf der der aktuelle Status, ein Bildschirmfoto des Themes, zusätzliche Informationen und eventuelle Einstellungen des Themes zu finden sind. +Genau wie Erweiterungen können Themes in der Übersichtsliste oder der Theme-Seite aktiviert bzw. deaktiviert werden. +Um ein Standardtheme für die Instanz zu wählen, benutze bitte die *Seiten* Bereich des Admin Panels. + +## Zusätzliche Features + +Es gibt einige optionale Features in Friendica, die Nutzer benutzen können oder halt nicht. +Zum Beispiel den *dislike* Button oder den *Webeditor* beim Erstellen von neuen Beiträgen. +In diesem Bereich des Admin Panels kannst du die Grundeinstellungen für diese Features festlegen und gegebenenfalls die Entscheidung treffen, dass Nutzer deiner Instanz diese auch nicht mehr ändern können. + +## DB Updates + +Wenn sich die Datenbankstruktur Friendicas ändert werden die Änderungen automatisch angewandt. +Solltest du den Verdacht haben, das eine Aktualisierung fehlgeschlagen ist, kannst du in diesem Bereich des Admin Panels den Status der Aktualisierungen überprüfen. + +## Warteschlange Inspizieren + +Auf der Eingangsseite des Admin Panels werden zwei Zahlen fpr die Warteschlangen angegeben. +Die zweite Zahl steht für die Beiträge, die initial nicht zugestellt werden konnten und später nochmal zugestellt werden sollen. +Sollte diese Zahl durch die Decke brechen, solltest du nachsehen an welchen Kontakt die Zustellung der Beiträge nicht funktioniert. + +Unter dem Menüpunkt "Warteschlange Inspizieren" findest du eine Liste dieser nicht zustellbaren Beiträge. +Diese Liste ist nach dem Empfänger sortiert. +Die Kommunikation zu dem Empfänger kann aus unterschiedlichen Gründen gestört sein. +Der andere Server könnte offline sein, oder gerade einfach nur eine hohe Systemlast aufweisen. + +Aber keine Panik! +Friendica wird die Beiträge nicht für alle Zeiten in der Warteschlange behalten. +Nach einiger Zeit werden Knoten als inaktiv identifiziert und Nachrichten an Nutzer dieser Knoten aus der Warteschlange gelöscht. + +## Federation Statistik + +Deine Instanz ist ein Teil eines Netzwerks von Servern dezentraler sozialer Netzwerke, der sogenannten **Federation**. +In diesem Bereich des Admin Panels findest du ein paar Zahlen zu dem Teil der Federation, die deine Instanz kennt. + +## Plugin Features + +Einige der Erweiterungen von Friendica benötigen global gültige Einstellungen, die der Administrator vornehmen muss. +Diese Erweiterungen sind hier aufgelistet, damit du die Einstellungen schneller findest. + +## Protokolle + +Dieser Bereich des Admin Panels ist auf zwei Seiten verteilt. +Die eine Seite dient der Konfiguration, die andere dem Anzeigen der Logs. + +Du solltest die Logdatei nicht in einem Verzeichnis anlegen, auf das man vom Internet aus zugreifen kann. +Wenn du das dennoch tun musste und die Standardeinstellungen des Apache Servers verwendest, dann solltest du darauf achten, dass die Logdateien mit der Endung `.log` oder `.out` enden. +Solltest du einen anderen Webserver verwenden, solltest du sicherstellen, dass der Zugrif zu Dateien mit diesen Endungen nicht möglich ist. + +Es gibt fünf Level der Ausführlichkeit mit denen Friendica arbeitet: Normal, Trace, Debug, Data und All. +Normalerweise solltest du für den Betrieb deiner Friendica Instanz keine Logs benötigen. +Wenn du versuchst einem Problem auf den Grund zu gehen, solltest du das "DEBUG" Level wählen. +Mit dem "All" Level schreibt Friendica alles in die Logdatei. +Die Datenmenge der geloggten Daten kann relativ schnell anwachsen, deshalb empfehlen wir das Anlegen von Protokollen nur zu aktivieren wenn es unbedingt nötig ist. + +**Die Größe der Logdateien kann schnell anwachsen**. +Du solltest deshalb einen Dienst zur [log rotation](https://en.wikipedia.org/wiki/Log_rotation) einrichten. + +**Bekannte Probleme**: Der Dateiname `friendica.log` kann bei speziellen Server Konfigurationen zu Problemen führen (siehe [issue 2209](https://github.com/friendica/friendica/issues/2209)). + +Normalerweise werden Fehler- und Warnmeldungen von PHP unterdrückt. +Wenn du sie aktivieren willst, musst du folgendes in der `.htconfig.php` Datei eintragen um die Meldungen in die Datei `php.out` zu speichern + + error_reporting(E_ERROR | E_WARNING | E_PARSE ); + ini_set('error_log','php.out'); + ini_set('log_errors','1'); + ini_set('display_errors', '0'); + +Die Datei `php.out` muss vom Webserver schreibbar sein und sollte ebenfalls außerhalb der Webverzeichnisse liegen. +Es kommt gelegentlich vor, dass nicht deklarierte Variablen referenziert werden, dehalb raten wir davon ab `E_NOTICE` oder `E_ALL` zu verwenden. +Die überwiegende Mehrzahl der auf diesen Stufen dokumentierten Fehler sind absolut harmlos. +Solltest du mit den oben empfohlenen Einstellungen Fehler finden, teile sie bitte den Entwicklern mit. +Im Allgemeinen sind dies Fehler, die behoben werden sollten. + +Solltest du eine leere (weiße) Seite vorfinden, während du Friendica nutzt, werfe bitte einen Blick in die PHP Logs. +Solche *White Screens* sind so gut wie immer ein Zeichen dafür, dass ein Fehler aufgetreten ist. + +## Diagnose + +In diesem Bereich des Admin Panels findest du zwei Werkzeuge mit der du untersuchen kannst, wie Friendica bestimmte Ressourcen einschätzt. +Diese Werkzeuge sind insbesondere bei der Analyse von Kommunikationsproblemen hilfreich. + +"Adresse untersuchen" zeigt Informationen zu einer URL an, wie Friendica sie wahrnimmt. + +Mit dem zweiten Werkzeug "Webfinger überprüfen" kannst du Informationen zu einem Ding anfordern, das über einen Webfinger ( jemand@example.com ) identifiziert wird. + +# Die Ausnahmen der Regel + +Für die oben genannte Regel gibt es vier Ausnahmen, deren Konfiguration nicht über das Admin Panel vorgenommen werden kann. +Dies sind die Datenbank Einstellungen, die Administrator Accounts, der PHP Pfad und die Konfiguration einer eventuellen Installation in ein Unterverzeichnis unterhalb der Hauptdomain. + +## Datenbank Einstellungen + +Mit den folgenden Einstellungen kannst du die Zugriffsdaten für den Datenbank Server festlegen. + + $db_host = 'your.db.host'; + $db_user = 'db_username'; + $db_pass = 'db_password'; + $db_data = 'database_name'; + +## Administratoren + +Du kannst einen, oder mehrere Accounts, zu Administratoren machen. +Normalerweise trifft dies auf den ersten Account zu, der nach der Installation angelegt wird. +Die Liste der E-Mail Adressen kann aber einfach erweitert werden. +Mit keiner der angegebenen E-Mail Adressen können weitere Accounts registriert werden. + + $a->config['admin_email'] = 'you@example.com, buddy@example.com'; + +## PHP Pfad + +Einige Prozesse von Friendica laufen im Hintergrund. +Für diese Prozesse muss der Pfad zu der PHP Version gesetzt sein, die verwendet werden soll. + + $a->config['php_path'] = '/pfad/zur/php-version'; + +## Unterverzeichnis Konfiguration + +Man kann Friendica in ein Unterverzeichnis des Webservers installieren. +Wir raten allerdings dringen davon ab, da es die Interoperabilität mit anderen Netzwerken (z.B. Diaspora, GNU Social, Hubzilla) verhindert. +Mal angenommen, du hast ein Unterverzeichnis tests und willst Friendica in ein weiteres Unterverzeichnis installieren, dann lautet die Konfiguration hierfür: + + $a->path = 'tests/friendica'; + +## Weitere Ausnahmen + +Es gibt noch einige experimentelle Einstellungen, die nur in der ``.htconfig.php`` Datei konfiguriert werden können. +Im [Konfigurationswerte, die nur in der .htconfig.php gesetzt werden können (EN)](help/htconfig) Artikel kannst du mehr darüber erfahren. diff --git a/doc/de/Text_editor.md b/doc/de/Text_editor.md index 0d9fbb5c74..33fc104dff 100644 --- a/doc/de/Text_editor.md +++ b/doc/de/Text_editor.md @@ -52,6 +52,8 @@ Cleanzero (inkl. smoothly, testbubble) +Frio + Frost Vier (inkl. dispy) diff --git a/doc/htconfig.md b/doc/htconfig.md index a36e0bef22..a7dd59d1f5 100644 --- a/doc/htconfig.md +++ b/doc/htconfig.md @@ -1,98 +1,115 @@ Config values that can only be set in .htconfig.php =================================================== -There are some config values that haven't found their way into the administration page. This has several reasons. Maybe they are part of a -current development that isn't considered stable and will be added later in the administration page when it is considered safe. Or it triggers -something that isn't expected to be of public interest. Or it is for testing purposes only. +* [Home](help) -**Attention:** Please be warned that you shouldn't use one of these values without the knowledge what it could trigger. Especially don't do that with -undocumented values. +There are some config values that haven't found their way into the administration page. +This has several reasons. +Maybe they are part of a current development that isn't considered stable and will be added later in the administration page when it is considered safe. +Or it triggers something that isn't expected to be of public interest. Or it is for testing purposes only. -The header of the section describes the category, the value is the parameter. Example: To set the directory value please add this -line to your .htconfig.php: +**Attention:** Please be warned that you shouldn't use one of these values without the knowledge what it could trigger. +Especially don't do that with undocumented values. + +The header of the section describes the category, the value is the parameter. +Example: To set the directory value please add this line to your .htconfig.php: $a->config['system']['directory'] = 'http://dir.friendi.ca'; +## jabber ## +* **debug** (Boolean) - Enable debug level for the jabber account synchronisation. +* **logfile** - Logfile for the jabber account synchronisation. +## system ## -## Jabber ## -* debug (Boolean) - Enable debug level for the jabber account synchronisation. -* logfile - Logfile for the jabber account synchronisation. - -## System ## - -* birthday_input_format - Default value is "ymd". -* block_local_dir (Boolean) - Blocks the access to the directory of the local users. -* default_service_class - -* delivery_batch_count - Number of deliveries per process. Default value is 1. (Disabled when using the worker) -* diaspora_test (Boolean) - For development only. Disables the message transfer. -* directory - The path to global directory. If not set then "http://dir.friendi.ca" is used. -* disable_email_validation (Boolean) - Disables the check if a mail address is in a valid format and can be resolved via DNS. -* disable_url_validation (Boolean) - Disables the DNS lookup of an URL. -* event_input_format - Default value is "ymd". -* ignore_cache (Boolean) - For development only. Disables the item cache. -* like_no_comment (Boolean) - Don't update the "commented" value of an item when it is liked. -* local_block (Boolean) - Used in conjunction with "block_public". -* local_search (Boolean) - Blocks the search for not logged in users to prevent crawlers from blocking your system. -* max_connections - The poller process isn't started when 3/4 of the possible database connections are used. When the system can't detect the maximum numbers of connection then this value can be used. -* max_contact_queue - Default value is 500. -* max_batch_queue - Default value is 1000. -* no_oembed (Boolean) - Don't use OEmbed to fetch more information about a link. -* no_oembed_rich_content (Boolean) - Don't show the rich content (e.g. embedded PDF). -* no_smilies (Boolean) - Don't show smilies. -* no_view_full_size (Boolean) - Don't add the link "View full size" under a resized image. -* optimize_items (Boolean) - Triggers an SQL command to optimize the item table before expiring items. -* ostatus_poll_timeframe - Defines how old an item can be to try to complete the conversation with it. -* paranoia (Boolean) - Log out users if their IP address changed. -* permit_crawling (Boolean) - Restricts the search for not logged in users to one search per minute. -* free_crawls - Number of "free" searches when "permit_crawling" is activated (Default value is 10) -* crawl_permit_period - Period in seconds between allowed searches when the number of free searches is reached and "permit_crawling" is activated (Default value is 60) -* png_quality - Default value is 8. -* proc_windows (Boolean) - Should be enabled if Friendica is running under Windows. -* proxy_cache_time - Time after which the cache is cleared. Default value is one day. -* pushpoll_frequency - -* qsearch_limit - Default value is 100. -* relay_server - Experimental Diaspora feature. Address of the relay server where public posts should be send to. For example https://podrelay.net -* relay_subscribe (Boolean) - Enables the receiving of public posts from the relay. They will be included in the search and on the community page when it is set up to show all public items. -* relay_scope - Can be "all" or "tags". "all" means that every public post should be received. "tags" means that only posts witt selected tags should be received. -* relay_server_tags - Comma separated list of tags for the "tags" subscription (see "relay_scrope") -* relay_user_tags (Boolean) - If enabled, the tags from the saved searches will used for the "tags" subscription in addition to the "relay_server_tags". -* remove_multiplicated_lines (Boolean) - If enabled, multiple linefeeds in items are stripped to a single one. -* show_unsupported_addons (Boolean) - Show all addons including the unsupported ones. -* show_unsupported_themes (Boolean) - Show all themes including the unsupported ones. -* throttle_limit_day - Maximum number of posts that a user can send per day with the API. -* throttle_limit_week - Maximum number of posts that a user can send per week with the API. -* throttle_limit_month - Maximum number of posts that a user can send per month with the API. -* wall-to-wall_share (Boolean) - Displays forwarded posts like "wall-to-wall" posts. -* xrd_timeout - Timeout for fetching the XRD links. Default value is 20 seconds. +* **allowed_link_protocols** (Array) - Allowed protocols in links URLs, add at your own risk. http is always allowed. +* **birthday_input_format** - Default value is "ymd". +* **block_local_dir** (Boolean) - Blocks the access to the directory of the local users. +* **curl_range_bytes** - Maximum number of bytes that should be fetched. Default is 0, which mean "no limit". +* **db_log** - Name of a logfile to log slow database queries +* **db_loglimit** - If a database call lasts longer than this value it is logged +* **db_log_index** - Name of a logfile to log queries with bad indexes +* **db_log_index_watch** - Watchlist of indexes to watch +* **db_loglimit_index** - Number of index rows needed to be logged for indexes on the watchlist +* **db_loglimit_index_high** - Number of index rows to be logged anyway (for any index) +* **db_log_index_blacklist** - Blacklist of indexes that shouldn't be watched +* **dbclean** (Boolean) - Enable the automatic database cleanup process +* **default_service_class** - +* **delivery_batch_count** - Number of deliveries per process. Default value is 1. (Disabled when using the worker) +* **diaspora_test** (Boolean) - For development only. Disables the message transfer. +* **directory** - The path to global directory. If not set then "http://dir.friendi.ca" is used. +* **disable_email_validation** (Boolean) - Disables the check if a mail address is in a valid format and can be resolved via DNS. +* **disable_url_validation** (Boolean) - Disables the DNS lookup of an URL. +* **event_input_format** - Default value is "ymd". +* **frontend_worker_timeout** - Value in minutes after we think that a frontend task was killed by the webserver. Default value is 10. +* **ignore_cache** (Boolean) - For development only. Disables the item cache. +* **like_no_comment** (Boolean) - Don't update the "commented" value of an item when it is liked. +* **local_block** (Boolean) - Used in conjunction with "block_public". +* **local_search** (Boolean) - Blocks the search for not logged in users to prevent crawlers from blocking your system. +* **max_connections** - The poller process isn't started when the maximum level of the possible database connections are used. When the system can't detect the maximum numbers of connection then this value can be used. +* **max_connections_level** - The maximum level of connections that are allowed to let the poller start. It is a percentage value. Default value is 75. +* **max_contact_queue** - Default value is 500. +* **max_batch_queue** - Default value is 1000. +* **max_processes_backend** - Maximum number of concurrent database processes for background tasks. Default value is 5. +* **max_processes_frontend** - Maximum number of concurrent database processes for foreground tasks. Default value is 20. +* **memcache** (Boolean) - Use memcache. To use memcache the PECL extension "memcache" has to be installed and activated. +* **memcache_host** - Hostname of the memcache daemon. Default is '127.0.0.1'. +* **memcache_port** - Portnumber of the memcache daemon. Default is 11211. +* **no_count** (Boolean) - Don't do count calculations (currently only when showing albums) +* **no_oembed** (Boolean) - Don't use OEmbed to fetch more information about a link. +* **no_oembed_rich_content** (Boolean) - Don't show the rich content (e.g. embedded PDF). +* **no_smilies** (Boolean) - Don't show smilies. +* **no_view_full_size** (Boolean) - Don't add the link "View full size" under a resized image. +* **optimize_items** (Boolean) - Triggers an SQL command to optimize the item table before expiring items. +* **ostatus_poll_timeframe** - Defines how old an item can be to try to complete the conversation with it. +* **paranoia** (Boolean) - Log out users if their IP address changed. +* **permit_crawling** (Boolean) - Restricts the search for not logged in users to one search per minute. +* **profiler** (Boolean) - Enable internal timings to help optimize code. Needed for "rendertime" addon. Default is false. +* **free_crawls** - Number of "free" searches when "permit_crawling" is activated (Default value is 10) +* **crawl_permit_period** - Period in seconds between allowed searches when the number of free searches is reached and "permit_crawling" is activated (Default value is 60) +* **png_quality** - Default value is 8. +* **proc_windows** (Boolean) - Should be enabled if Friendica is running under Windows. +* **proxy_cache_time** - Time after which the cache is cleared. Default value is one day. +* **pushpoll_frequency** - +* **qsearch_limit** - Default value is 100. +* **relay_server** - Experimental Diaspora feature. Address of the relay server where public posts should be send to. For example https://podrelay.net +* **relay_subscribe** (Boolean) - Enables the receiving of public posts from the relay. They will be included in the search and on the community page when it is set up to show all public items. +* **relay_scope** - Can be "all" or "tags". "all" means that every public post should be received. "tags" means that only posts with selected tags should be received. +* **relay_server_tags** - Comma separated list of tags for the "tags" subscription (see "relay_scrope") +* **relay_user_tags** (Boolean) - If enabled, the tags from the saved searches will used for the "tags" subscription in addition to the "relay_server_tags". +* **remove_multiplicated_lines** (Boolean) - If enabled, multiple linefeeds in items are stripped to a single one. +* **show_unsupported_addons** (Boolean) - Show all addons including the unsupported ones. +* **show_unsupported_themes** (Boolean) - Show all themes including the unsupported ones. +* **throttle_limit_day** - Maximum number of posts that a user can send per day with the API. +* **throttle_limit_week** - Maximum number of posts that a user can send per week with the API. +* **throttle_limit_month** - Maximum number of posts that a user can send per month with the API. +* **wall-to-wall_share** (Boolean) - Displays forwarded posts like "wall-to-wall" posts. +* **worker_cooldown** - Cooldown time after each worker function call. Default value is 0 seconds. +* **xrd_timeout** - Timeout for fetching the XRD links. Default value is 20 seconds. ## service_class ## -* upgrade_link - +* **upgrade_link** - ## experimentals ## -* exp_themes (Boolean) - Show experimental themes as well. +* **exp_themes** (Boolean) - Show experimental themes as well. ## theme ## -* hide_eventlist (Boolean) - Don't show the birthdays and events on the profile and network page +* **hide_eventlist** (Boolean) - Don't show the birthdays and events on the profile and network page # Administrator Options # -Enabling the admin panel for an account, and thus making the account holder -admin of the node, is done by setting the variable +Enabling the admin panel for an account, and thus making the account holder admin of the node, is done by setting the variable $a->config['admin_email'] = "someone@example.com"; -where you have to match the email address used for the account with the one you -enter to the .htconfig file. If more then one account should be able to access -the admin panel, seperate the email addresses with a comma. +Where you have to match the email address used for the account with the one you enter to the .htconfig file. +If more then one account should be able to access the admin panel, seperate the email addresses with a comma. $a->config['admin_email'] = "someone@example.com,someonelese@example.com"; -If you want to have a more personalized closing line for the notification -emails you can set a variable for the admin_name. +If you want to have a more personalized closing line for the notification emails you can set a variable for the admin_name. $a->config['admin_name'] = "Marvin"; - diff --git a/doc/img/editor_frio.png b/doc/img/editor_frio.png new file mode 100644 index 0000000000..8428b34382 Binary files /dev/null and b/doc/img/editor_frio.png differ diff --git a/doc/readme.md b/doc/readme.md index 068d0c9c5c..98b637a22e 100644 --- a/doc/readme.md +++ b/doc/readme.md @@ -27,7 +27,7 @@ Friendica Documentation and Resources **Technical Documentation** * [Install](help/Install) -* [Settings](help/Settings) +* [Settings & Admin Panel](help/Settings) * [Plugins](help/Plugins) * [Installing Connectors (Twitter/GNU Social)](help/Installing-Connectors) * [Install an ejabberd server (XMPP chat) with synchronized credentials](help/install-ejabberd) diff --git a/doc/themes.md b/doc/themes.md index add44c776b..b553debfd7 100644 --- a/doc/themes.md +++ b/doc/themes.md @@ -122,10 +122,11 @@ the 1st part of the line is the name of the CSS file (without the .css) the 2nd Calling the t() function with the common name makes the string translateable. The selected 1st part will be saved in the database by the theme_post function. - function theme_post(&$a){ + function theme_post(App $a){ // non local users shall not pass - if(! local_user()) + if (! local_user()) { return; + } // if the one specific submit button was pressed then proceed if (isset($_POST['duepuntozero-settings-submit'])){ // and save the selection key into the personal config of the user @@ -167,7 +168,7 @@ The content of this file should be something like theme_info = array( 'extends' => 'duepuntozero'. ); @@ -250,7 +251,7 @@ Next crucial part of the theme.php file is a definition of an init function. The name of the function is
Shiny trinkets are shiny.
+ * + * @endverbatim + */ + public static function getSiteinfo($url, $no_guessing = false, $do_oembed = true, $count = 1) { + + $a = get_app(); + + $siteinfo = array(); + + // Check if the URL does contain a scheme + $scheme = parse_url($url, PHP_URL_SCHEME); + + if ($scheme == "") { + $url = "http://".trim($url, "/"); + } + + if ($count > 10) { + logger("parseurl_getsiteinfo: Endless loop detected for ".$url, LOGGER_DEBUG); + return($siteinfo); + } + + $url = trim($url, "'"); + $url = trim($url, '"'); + + $url = strip_tracking_query_params($url); + + $siteinfo["url"] = $url; + $siteinfo["type"] = "link"; + + $check_cert = Config::get("system", "verifyssl"); + + $stamp1 = microtime(true); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 10); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_USERAGENT, $a->get_useragent()); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false)); + if ($check_cert) { + @curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); + } + + $header = curl_exec($ch); + $curl_info = @curl_getinfo($ch); + curl_close($ch); + + $a->save_timestamp($stamp1, "network"); + + if ((($curl_info["http_code"] == "301") || ($curl_info["http_code"] == "302") || ($curl_info["http_code"] == "303") || ($curl_info["http_code"] == "307")) + && (($curl_info["redirect_url"] != "") || ($curl_info["location"] != ""))) { + if ($curl_info["redirect_url"] != "") { + $siteinfo = self::getSiteinfo($curl_info["redirect_url"], $no_guessing, $do_oembed, ++$count); + } else { + $siteinfo = self::getSiteinfo($curl_info["location"], $no_guessing, $do_oembed, ++$count); + } + return($siteinfo); + } + + // If the file is too large then exit + if ($curl_info["download_content_length"] > 1000000) { + return($siteinfo); + } + + // If it isn't a HTML file then exit + if (($curl_info["content_type"] != "") && !strstr(strtolower($curl_info["content_type"]), "html")) { + return($siteinfo); + } + + if ($do_oembed) { + + $oembed_data = oembed_fetch_url($url); + + if (!in_array($oembed_data->type, array("error", "rich"))) { + $siteinfo["type"] = $oembed_data->type; + } + + if (($oembed_data->type == "link") && ($siteinfo["type"] != "photo")) { + if (isset($oembed_data->title)) { + $siteinfo["title"] = $oembed_data->title; + } + if (isset($oembed_data->description)) { + $siteinfo["text"] = trim($oembed_data->description); + } + if (isset($oembed_data->thumbnail_url)) { + $siteinfo["image"] = $oembed_data->thumbnail_url; + } + } + } + + // Fetch the first mentioned charset. Can be in body or header + $charset = ""; + if (preg_match('/charset=(.*?)['."'".'"\s\n]/', $header, $matches)) { + $charset = trim(trim(trim(array_pop($matches)), ';,')); + } + + if ($charset == "") { + $charset = "utf-8"; + } + + $pos = strpos($header, "\r\n\r\n"); + + if ($pos) { + $body = trim(substr($header, $pos)); + } else { + $body = $header; + } + + if (($charset != "") && (strtoupper($charset) != "UTF-8")) { + logger("parseurl_getsiteinfo: detected charset ".$charset, LOGGER_DEBUG); + //$body = mb_convert_encoding($body, "UTF-8", $charset); + $body = iconv($charset, "UTF-8//TRANSLIT", $body); + } + + $body = mb_convert_encoding($body, 'HTML-ENTITIES', "UTF-8"); + + $doc = new \DOMDocument(); + @$doc->loadHTML($body); + + \xml::deleteNode($doc, "style"); + \xml::deleteNode($doc, "script"); + \xml::deleteNode($doc, "option"); + \xml::deleteNode($doc, "h1"); + \xml::deleteNode($doc, "h2"); + \xml::deleteNode($doc, "h3"); + \xml::deleteNode($doc, "h4"); + \xml::deleteNode($doc, "h5"); + \xml::deleteNode($doc, "h6"); + \xml::deleteNode($doc, "ol"); + \xml::deleteNode($doc, "ul"); + + $xpath = new \DomXPath($doc); + + $list = $xpath->query("//meta[@content]"); + foreach ($list as $node) { + $attr = array(); + if ($node->attributes->length) { + foreach ($node->attributes as $attribute) { + $attr[$attribute->name] = $attribute->value; + } + } + + if (@$attr["http-equiv"] == "refresh") { + $path = $attr["content"]; + $pathinfo = explode(";", $path); + $content = ""; + foreach ($pathinfo as $value) { + if (substr(strtolower($value), 0, 4) == "url=") { + $content = substr($value, 4); + } + } + if ($content != "") { + $siteinfo = self::getSiteinfo($content, $no_guessing, $do_oembed, ++$count); + return($siteinfo); + } + } + } + + $list = $xpath->query("//title"); + if ($list->length > 0) { + $siteinfo["title"] = $list->item(0)->nodeValue; + } + + //$list = $xpath->query("head/meta[@name]"); + $list = $xpath->query("//meta[@name]"); + foreach ($list as $node) { + $attr = array(); + if ($node->attributes->length) { + foreach ($node->attributes as $attribute) { + $attr[$attribute->name] = $attribute->value; + } + } + + $attr["content"] = trim(html_entity_decode($attr["content"], ENT_QUOTES, "UTF-8")); + + if ($attr["content"] != "") { + switch (strtolower($attr["name"])) { + case "fulltitle": + $siteinfo["title"] = $attr["content"]; + break; + case "description": + $siteinfo["text"] = $attr["content"]; + break; + case "thumbnail": + $siteinfo["image"] = $attr["content"]; + break; + case "twitter:image": + $siteinfo["image"] = $attr["content"]; + break; + case "twitter:image:src": + $siteinfo["image"] = $attr["content"]; + break; + case "twitter:card": + if (($siteinfo["type"] == "") || ($attr["content"] == "photo")) { + $siteinfo["type"] = $attr["content"]; + } + break; + case "twitter:description": + $siteinfo["text"] = $attr["content"]; + break; + case "twitter:title": + $siteinfo["title"] = $attr["content"]; + break; + case "dc.title": + $siteinfo["title"] = $attr["content"]; + break; + case "dc.description": + $siteinfo["text"] = $attr["content"]; + break; + case "keywords": + $keywords = explode(",", $attr["content"]); + break; + case "news_keywords": + $keywords = explode(",", $attr["content"]); + break; + } + } + if ($siteinfo["type"] == "summary") { + $siteinfo["type"] = "link"; + } + } + + if (isset($keywords)) { + $siteinfo["keywords"] = array(); + foreach ($keywords as $keyword) { + if (!in_array(trim($keyword), $siteinfo["keywords"])) { + $siteinfo["keywords"][] = trim($keyword); + } + } + } + + //$list = $xpath->query("head/meta[@property]"); + $list = $xpath->query("//meta[@property]"); + foreach ($list as $node) { + $attr = array(); + if ($node->attributes->length) { + foreach ($node->attributes as $attribute) { + $attr[$attribute->name] = $attribute->value; + } + } + + $attr["content"] = trim(html_entity_decode($attr["content"], ENT_QUOTES, "UTF-8")); + + if ($attr["content"] != "") { + switch (strtolower($attr["property"])) { + case "og:image": + $siteinfo["image"] = $attr["content"]; + break; + case "og:title": + $siteinfo["title"] = $attr["content"]; + break; + case "og:description": + $siteinfo["text"] = $attr["content"]; + break; + } + } + } + + if ((@$siteinfo["image"] == "") && !$no_guessing) { + $list = $xpath->query("//img[@src]"); + foreach ($list as $node) { + $attr = array(); + if ($node->attributes->length) { + foreach ($node->attributes as $attribute) { + $attr[$attribute->name] = $attribute->value; + } + } + + $src = self::completeUrl($attr["src"], $url); + $photodata = get_photo_info($src); + + if (($photodata) && ($photodata[0] > 150) && ($photodata[1] > 150)) { + if ($photodata[0] > 300) { + $photodata[1] = round($photodata[1] * (300 / $photodata[0])); + $photodata[0] = 300; + } + if ($photodata[1] > 300) { + $photodata[0] = round($photodata[0] * (300 / $photodata[1])); + $photodata[1] = 300; + } + $siteinfo["images"][] = array("src" => $src, + "width" => $photodata[0], + "height" => $photodata[1]); + } + + } + } elseif ($siteinfo["image"] != "") { + $src = self::completeUrl($siteinfo["image"], $url); + + unset($siteinfo["image"]); + + $photodata = get_photo_info($src); + + if (($photodata) && ($photodata[0] > 10) && ($photodata[1] > 10)) { + $siteinfo["images"][] = array("src" => $src, + "width" => $photodata[0], + "height" => $photodata[1]); + } + } + + if ((@$siteinfo["text"] == "") && (@$siteinfo["title"] != "") && !$no_guessing) { + $text = ""; + + $list = $xpath->query("//div[@class='article']"); + foreach ($list as $node) { + if (strlen($node->nodeValue) > 40) { + $text .= " ".trim($node->nodeValue); + } + } + + if ($text == "") { + $list = $xpath->query("//div[@class='content']"); + foreach ($list as $node) { + if (strlen($node->nodeValue) > 40) { + $text .= " ".trim($node->nodeValue); + } + } + } + + // If none text was found then take the paragraph content + if ($text == "") { + $list = $xpath->query("//p"); + foreach ($list as $node) { + if (strlen($node->nodeValue) > 40) { + $text .= " ".trim($node->nodeValue); + } + } + } + + if ($text != "") { + $text = trim(str_replace(array("\n", "\r"), array(" ", " "), $text)); + + while (strpos($text, " ")) { + $text = trim(str_replace(" ", " ", $text)); + } + + $siteinfo["text"] = trim(html_entity_decode(substr($text, 0, 350), ENT_QUOTES, "UTF-8").'...'); + } + } + + logger("parseurl_getsiteinfo: Siteinfo for ".$url." ".print_r($siteinfo, true), LOGGER_DEBUG); + + call_hooks("getsiteinfo", $siteinfo); + + return($siteinfo); + } + + /** + * @brief Convert tags from CSV to an array + * + * @param string $string Tags + * @return array with formatted Hashtags + */ + public static function convertTagsToArray($string) { + $arr_tags = str_getcsv($string); + if (count($arr_tags)) { + // add the # sign to every tag + array_walk($arr_tags, array("self", "arrAddHashes")); + + return $arr_tags; + } + } + + /** + * @brief Add a hasht sign to a string + * + * This method is used as callback function + * + * @param string $tag The pure tag name + * @param int $k Counter for internal use + */ + private static function arrAddHashes(&$tag, $k) { + $tag = "#" . $tag; + } + + /** + * @brief Add a scheme to an url + * + * The src attribute of some html elements (e.g. images) + * can miss the scheme so we need to add the correct + * scheme + * + * @param string $url The url which possibly does have + * a missing scheme (a link to an image) + * @param string $scheme The url with a correct scheme + * (e.g. the url from the webpage which does contain the image) + * + * @return string The url with a scheme + */ + private static function completeUrl($url, $scheme) { + $urlarr = parse_url($url); + + // If the url does allready have an scheme + // we can stop the process here + if (isset($urlarr["scheme"])) { + return($url); + } + + $schemearr = parse_url($scheme); + + $complete = $schemearr["scheme"]."://".$schemearr["host"]; + + if (@$schemearr["port"] != "") { + $complete .= ":".$schemearr["port"]; + } + + if (strpos($urlarr["path"],"/") !== 0) { + $complete .= "/"; + } + + $complete .= $urlarr["path"]; + + if (@$urlarr["query"] != "") { + $complete .= "?".$urlarr["query"]; + } + + if (@$urlarr["fragment"] != "") { + $complete .= "#".$urlarr["fragment"]; + } + + return($complete); + } +} diff --git a/include/Photo.php b/include/Photo.php index 91fce55a86..828dce82d7 100644 --- a/include/Photo.php +++ b/include/Photo.php @@ -1,133 +1,141 @@ 'jpg', - 'image/png' => 'png', - 'image/gif' => 'gif' - ); - } else { - $t = array(); - $t['image/jpeg'] ='jpg'; - if (imagetypes() & IMG_PNG) $t['image/png'] = 'png'; + /** + * @brief supported mimetypes and corresponding file extensions + */ + static function supportedTypes() { + if (class_exists('Imagick')) { + + // Imagick::queryFormats won't help us a lot there... + // At least, not yet, other parts of friendica uses this array + $t = array( + 'image/jpeg' => 'jpg', + 'image/png' => 'png', + 'image/gif' => 'gif' + ); + } else { + $t = array(); + $t['image/jpeg'] ='jpg'; + if (imagetypes() & IMG_PNG) { + $t['image/png'] = 'png'; + } + } + + return $t; } - return $t; - } + public function __construct($data, $type=null) { + $this->imagick = class_exists('Imagick'); + $this->types = $this->supportedTypes(); + if (!array_key_exists($type, $this->types)){ + $type='image/jpeg'; + } + $this->type = $type; - public function __construct($data, $type=null) { - $this->imagick = class_exists('Imagick'); - $this->types = $this->supportedTypes(); - if (!array_key_exists($type,$this->types)){ - $type='image/jpeg'; - } - $this->type = $type; - - if($this->is_imagick() && $this->load_data($data)) { + if ($this->is_imagick() && $this->load_data($data)) { return true; } else { // Failed to load with Imagick, fallback $this->imagick = false; } return $this->load_data($data); - } - - public function __destruct() { - if($this->image) { - if($this->is_imagick()) { - $this->image->clear(); - $this->image->destroy(); - return; - } - imagedestroy($this->image); } - } - public function is_imagick() { - return $this->imagick; - } - - /** - * Maps Mime types to Imagick formats - */ - public function get_FormatsMap() { - $m = array( - 'image/jpeg' => 'JPG', - 'image/png' => 'PNG', - 'image/gif' => 'GIF' - ); - return $m; - } - - private function load_data($data) { - if($this->is_imagick()) { - $this->image = new Imagick(); - try { - $this->image->readImageBlob($data); + public function __destruct() { + if ($this->image) { + if ($this->is_imagick()) { + $this->image->clear(); + $this->image->destroy(); + return; } - catch (Exception $e) { + imagedestroy($this->image); + } + } + + public function is_imagick() { + return $this->imagick; + } + + /** + * @brief Maps Mime types to Imagick formats + * @return arr With with image formats (mime type as key) + */ + public function get_FormatsMap() { + $m = array( + 'image/jpeg' => 'JPG', + 'image/png' => 'PNG', + 'image/gif' => 'GIF' + ); + return $m; + } + + private function load_data($data) { + if ($this->is_imagick()) { + $this->image = new Imagick(); + try { + $this->image->readImageBlob($data); + } catch (Exception $e) { // Imagick couldn't use the data return false; } - /** - * Setup the image to the format it will be saved to - */ - $map = $this->get_FormatsMap(); - $format = $map[$type]; - $this->image->setFormat($format); + /* + * Setup the image to the format it will be saved to + */ + $map = $this->get_FormatsMap(); + $format = $map[$type]; + $this->image->setFormat($format); - // Always coalesce, if it is not a multi-frame image it won't hurt anyway - $this->image = $this->image->coalesceImages(); + // Always coalesce, if it is not a multi-frame image it won't hurt anyway + $this->image = $this->image->coalesceImages(); - /** - * setup the compression here, so we'll do it only once - */ - switch($this->getType()){ - case "image/png": - $quality = get_config('system','png_quality'); - if((! $quality) || ($quality > 9)) - $quality = PNG_QUALITY; - /** - * From http://www.imagemagick.org/script/command-line-options.php#quality: - * - * 'For the MNG and PNG image formats, the quality value sets - * the zlib compression level (quality / 10) and filter-type (quality % 10). - * The default PNG "quality" is 75, which means compression level 7 with adaptive PNG filtering, - * unless the image has a color map, in which case it means compression level 7 with no PNG filtering' - */ - $quality = $quality * 10; - $this->image->setCompressionQuality($quality); - break; - case "image/jpeg": - $quality = get_config('system','jpeg_quality'); - if((! $quality) || ($quality > 100)) - $quality = JPEG_QUALITY; - $this->image->setCompressionQuality($quality); - } + /* + * setup the compression here, so we'll do it only once + */ + switch($this->getType()){ + case "image/png": + $quality = get_config('system', 'png_quality'); + if ((! $quality) || ($quality > 9)) { + $quality = PNG_QUALITY; + } + /* + * From http://www.imagemagick.org/script/command-line-options.php#quality: + * + * 'For the MNG and PNG image formats, the quality value sets + * the zlib compression level (quality / 10) and filter-type (quality % 10). + * The default PNG "quality" is 75, which means compression level 7 with adaptive PNG filtering, + * unless the image has a color map, in which case it means compression level 7 with no PNG filtering' + */ + $quality = $quality * 10; + $this->image->setCompressionQuality($quality); + break; + case "image/jpeg": + $quality = get_config('system', 'jpeg_quality'); + if ((! $quality) || ($quality > 100)) { + $quality = JPEG_QUALITY; + } + $this->image->setCompressionQuality($quality); + } // The 'width' and 'height' properties are only used by non-Imagick routines. $this->width = $this->image->getImageWidth(); @@ -139,7 +147,7 @@ class Photo { $this->valid = false; $this->image = @imagecreatefromstring($data); - if($this->image !== FALSE) { + if ($this->image !== false) { $this->width = imagesx($this->image); $this->height = imagesy($this->image); $this->valid = true; @@ -148,123 +156,125 @@ class Photo { return true; } - + return false; } - public function is_valid() { - if($this->is_imagick()) - return ($this->image !== FALSE); - return $this->valid; - } - - public function getWidth() { - if(!$this->is_valid()) - return FALSE; - - if($this->is_imagick()) - return $this->image->getImageWidth(); - return $this->width; - } - - public function getHeight() { - if(!$this->is_valid()) - return FALSE; - - if($this->is_imagick()) - return $this->image->getImageHeight(); - return $this->height; - } - - public function getImage() { - if(!$this->is_valid()) - return FALSE; - - if($this->is_imagick()) { - /* Clean it */ - $this->image = $this->image->deconstructImages(); - return $this->image; + public function is_valid() { + if ($this->is_imagick()) { + return ($this->image !== false); + } + return $this->valid; } - return $this->image; - } - public function getType() { - if(!$this->is_valid()) - return FALSE; + public function getWidth() { + if (!$this->is_valid()) { + return false; + } - return $this->type; - } + if ($this->is_imagick()) { + return $this->image->getImageWidth(); + } + return $this->width; + } - public function getExt() { - if(!$this->is_valid()) - return FALSE; + public function getHeight() { + if (!$this->is_valid()) { + return false; + } - return $this->types[$this->getType()]; - } + if ($this->is_imagick()) { + return $this->image->getImageHeight(); + } + return $this->height; + } - public function scaleImage($max) { - if(!$this->is_valid()) - return FALSE; + public function getImage() { + if (!$this->is_valid()) { + return false; + } - $width = $this->getWidth(); - $height = $this->getHeight(); + if ($this->is_imagick()) { + /* Clean it */ + $this->image = $this->image->deconstructImages(); + return $this->image; + } + return $this->image; + } - $dest_width = $dest_height = 0; + public function getType() { + if (!$this->is_valid()) { + return false; + } - if((! $width)|| (! $height)) - return FALSE; + return $this->type; + } - if($width > $max && $height > $max) { + public function getExt() { + if (!$this->is_valid()) { + return false; + } + + return $this->types[$this->getType()]; + } + + public function scaleImage($max) { + if (!$this->is_valid()) { + return false; + } + + $width = $this->getWidth(); + $height = $this->getHeight(); + + $dest_width = $dest_height = 0; + + if ((! $width)|| (! $height)) { + return false; + } + + if ($width > $max && $height > $max) { // very tall image (greater than 16:9) // constrain the width - let the height float. - if((($height * 9) / 16) > $width) { + if ((($height * 9) / 16) > $width) { $dest_width = $max; - $dest_height = intval(( $height * $max ) / $width); + $dest_height = intval(($height * $max) / $width); + } elseif ($width > $height) { + // else constrain both dimensions + $dest_width = $max; + $dest_height = intval(($height * $max) / $width); + } else { + $dest_width = intval(($width * $max) / $height); + $dest_height = $max; } - - // else constrain both dimensions - - elseif($width > $height) { - $dest_width = $max; - $dest_height = intval(( $height * $max ) / $width); - } - else { - $dest_width = intval(( $width * $max ) / $height); - $dest_height = $max; - } - } - else { - if( $width > $max ) { - $dest_width = $max; - $dest_height = intval(( $height * $max ) / $width); - } - else { - if( $height > $max ) { + } else { + if ($width > $max) { + $dest_width = $max; + $dest_height = intval(($height * $max) / $width); + } else { + if ($height > $max) { // very tall image (greater than 16:9) // but width is OK - don't do anything - if((($height * 9) / 16) > $width) { + if ((($height * 9) / 16) > $width) { $dest_width = $width; - $dest_height = $height; - } - else { - $dest_width = intval(( $width * $max ) / $height); - $dest_height = $max; + $dest_height = $height; + } else { + $dest_width = intval(($width * $max) / $height); + $dest_height = $max; } + } else { + $dest_width = $width; + $dest_height = $height; + } + } } - else { - $dest_width = $width; - $dest_height = $height; - } - } - } - if($this->is_imagick()) { - /** + if ($this->is_imagick()) { + /* * If it is not animated, there will be only one iteration here, * so don't bother checking */ @@ -273,7 +283,7 @@ class Photo { do { // FIXME - implement horizantal bias for scaling as in followin GD functions - // to allow very tall images to be constrained only horizontally. + // to allow very tall images to be constrained only horizontally. $this->image->scaleImage($dest_width, $dest_height); } while ($this->image->nextImage()); @@ -283,234 +293,253 @@ class Photo { $this->height = $this->image->getImageHeight(); return; - } - - - $dest = imagecreatetruecolor( $dest_width, $dest_height ); - imagealphablending($dest, false); - imagesavealpha($dest, true); - if ($this->type=='image/png') imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha - imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dest_width, $dest_height, $width, $height); - if($this->image) - imagedestroy($this->image); - $this->image = $dest; - $this->width = imagesx($this->image); - $this->height = imagesy($this->image); - } - - public function rotate($degrees) { - if(!$this->is_valid()) - return FALSE; - - if($this->is_imagick()) { - $this->image->setFirstIterator(); - do { - $this->image->rotateImage(new ImagickPixel(), -$degrees); // ImageMagick rotates in the opposite direction of imagerotate() - } while ($this->image->nextImage()); - return; - } - - $this->image = imagerotate($this->image,$degrees,0); - $this->width = imagesx($this->image); - $this->height = imagesy($this->image); - } - - public function flip($horiz = true, $vert = false) { - if(!$this->is_valid()) - return FALSE; - - if($this->is_imagick()) { - $this->image->setFirstIterator(); - do { - if($horiz) $this->image->flipImage(); - if($vert) $this->image->flopImage(); - } while ($this->image->nextImage()); - return; - } - - $w = imagesx($this->image); - $h = imagesy($this->image); - $flipped = imagecreate($w, $h); - if($horiz) { - for ($x = 0; $x < $w; $x++) { - imagecopy($flipped, $this->image, $x, 0, $w - $x - 1, 0, 1, $h); - } - } - if($vert) { - for ($y = 0; $y < $h; $y++) { - imagecopy($flipped, $this->image, 0, $y, 0, $h - $y - 1, $w, 1); - } - } - $this->image = $flipped; - } - - public function orient($filename) { - if ($this->is_imagick()) { - // based off comment on http://php.net/manual/en/imagick.getimageorientation.php - $orientation = $this->image->getImageOrientation(); - switch ($orientation) { - case imagick::ORIENTATION_BOTTOMRIGHT: - $this->image->rotateimage("#000", 180); - break; - case imagick::ORIENTATION_RIGHTTOP: - $this->image->rotateimage("#000", 90); - break; - case imagick::ORIENTATION_LEFTBOTTOM: - $this->image->rotateimage("#000", -90); - break; } - $this->image->setImageOrientation(imagick::ORIENTATION_TOPLEFT); - return TRUE; - } - // based off comment on http://php.net/manual/en/function.imagerotate.php - if(!$this->is_valid()) - return FALSE; - - if( (! function_exists('exif_read_data')) || ($this->getType() !== 'image/jpeg') ) - return; - - $exif = @exif_read_data($filename,null,true); - if(! $exif) - return; - - $ort = $exif['IFD0']['Orientation']; - - switch($ort) - { - case 1: // nothing - break; - - case 2: // horizontal flip - $this->flip(); - break; - - case 3: // 180 rotate left - $this->rotate(180); - break; - - case 4: // vertical flip - $this->flip(false, true); - break; - - case 5: // vertical flip + 90 rotate right - $this->flip(false, true); - $this->rotate(-90); - break; - - case 6: // 90 rotate right - $this->rotate(-90); - break; - - case 7: // horizontal flip + 90 rotate right - $this->flip(); - $this->rotate(-90); - break; - - case 8: // 90 rotate left - $this->rotate(90); - break; - } - - // logger('exif: ' . print_r($exif,true)); - return $exif; - - } - - - - public function scaleImageUp($min) { - if(!$this->is_valid()) - return FALSE; - - - $width = $this->getWidth(); - $height = $this->getHeight(); - - $dest_width = $dest_height = 0; - - if((! $width)|| (! $height)) - return FALSE; - - if($width < $min && $height < $min) { - if($width > $height) { - $dest_width = $min; - $dest_height = intval(( $height * $min ) / $width); - } - else { - $dest_width = intval(( $width * $min ) / $height); - $dest_height = $min; - } - } - else { - if( $width < $min ) { - $dest_width = $min; - $dest_height = intval(( $height * $min ) / $width); - } - else { - if( $height < $min ) { - $dest_width = intval(( $width * $min ) / $height); - $dest_height = $min; + $dest = imagecreatetruecolor($dest_width, $dest_height); + imagealphablending($dest, false); + imagesavealpha($dest, true); + if ($this->type=='image/png') { + imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha } - else { - $dest_width = $width; - $dest_height = $height; + imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dest_width, $dest_height, $width, $height); + if ($this->image) { + imagedestroy($this->image); } - } + $this->image = $dest; + $this->width = imagesx($this->image); + $this->height = imagesy($this->image); } - if($this->is_imagick()) - return $this->scaleImage($dest_width,$dest_height); + public function rotate($degrees) { + if (!$this->is_valid()) { + return false; + } - $dest = imagecreatetruecolor( $dest_width, $dest_height ); - imagealphablending($dest, false); - imagesavealpha($dest, true); - if ($this->type=='image/png') imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha - imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dest_width, $dest_height, $width, $height); - if($this->image) - imagedestroy($this->image); - $this->image = $dest; - $this->width = imagesx($this->image); - $this->height = imagesy($this->image); - } + if ($this->is_imagick()) { + $this->image->setFirstIterator(); + do { + $this->image->rotateImage(new ImagickPixel(), -$degrees); // ImageMagick rotates in the opposite direction of imagerotate() + } while ($this->image->nextImage()); + return; + } - - - public function scaleImageSquare($dim) { - if(!$this->is_valid()) - return FALSE; - - if($this->is_imagick()) { - $this->image->setFirstIterator(); - do { - $this->image->scaleImage($dim, $dim); - } while ($this->image->nextImage()); - return; + $this->image = imagerotate($this->image,$degrees,0); + $this->width = imagesx($this->image); + $this->height = imagesy($this->image); } - $dest = imagecreatetruecolor( $dim, $dim ); - imagealphablending($dest, false); - imagesavealpha($dest, true); - if ($this->type=='image/png') imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha - imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dim, $dim, $this->width, $this->height); - if($this->image) - imagedestroy($this->image); - $this->image = $dest; - $this->width = imagesx($this->image); - $this->height = imagesy($this->image); - } + public function flip($horiz = true, $vert = false) { + if (!$this->is_valid()) { + return false; + } + + if ($this->is_imagick()) { + $this->image->setFirstIterator(); + do { + if ($horiz) { + $this->image->flipImage(); + } + if ($vert) { + $this->image->flopImage(); + } + } while ($this->image->nextImage()); + return; + } + + $w = imagesx($this->image); + $h = imagesy($this->image); + $flipped = imagecreate($w, $h); + if ($horiz) { + for ($x = 0; $x < $w; $x++) { + imagecopy($flipped, $this->image, $x, 0, $w - $x - 1, 0, 1, $h); + } + } + if ($vert) { + for ($y = 0; $y < $h; $y++) { + imagecopy($flipped, $this->image, 0, $y, 0, $h - $y - 1, $w, 1); + } + } + $this->image = $flipped; + } + + public function orient($filename) { + if ($this->is_imagick()) { + // based off comment on http://php.net/manual/en/imagick.getimageorientation.php + $orientation = $this->image->getImageOrientation(); + switch ($orientation) { + case imagick::ORIENTATION_BOTTOMRIGHT: + $this->image->rotateimage("#000", 180); + break; + case imagick::ORIENTATION_RIGHTTOP: + $this->image->rotateimage("#000", 90); + break; + case imagick::ORIENTATION_LEFTBOTTOM: + $this->image->rotateimage("#000", -90); + break; + } + + $this->image->setImageOrientation(imagick::ORIENTATION_TOPLEFT); + return true; + } + // based off comment on http://php.net/manual/en/function.imagerotate.php + + if (!$this->is_valid()) { + return false; + } + + if ((!function_exists('exif_read_data')) || ($this->getType() !== 'image/jpeg')) { + return; + } + + $exif = @exif_read_data($filename,null,true); + if (!$exif) { + return; + } + + $ort = $exif['IFD0']['Orientation']; + + switch($ort) + { + case 1: // nothing + break; + + case 2: // horizontal flip + $this->flip(); + break; + + case 3: // 180 rotate left + $this->rotate(180); + break; + + case 4: // vertical flip + $this->flip(false, true); + break; + + case 5: // vertical flip + 90 rotate right + $this->flip(false, true); + $this->rotate(-90); + break; + + case 6: // 90 rotate right + $this->rotate(-90); + break; + + case 7: // horizontal flip + 90 rotate right + $this->flip(); + $this->rotate(-90); + break; + + case 8: // 90 rotate left + $this->rotate(90); + break; + } + + // logger('exif: ' . print_r($exif,true)); + return $exif; + + } - public function cropImage($max,$x,$y,$w,$h) { - if(!$this->is_valid()) - return FALSE; - if($this->is_imagick()) { + public function scaleImageUp($min) { + if (!$this->is_valid()) { + return false; + } + + + $width = $this->getWidth(); + $height = $this->getHeight(); + + $dest_width = $dest_height = 0; + + if ((!$width)|| (!$height)) { + return false; + } + + if ($width < $min && $height < $min) { + if ($width > $height) { + $dest_width = $min; + $dest_height = intval(($height * $min) / $width); + } else { + $dest_width = intval(($width * $min) / $height); + $dest_height = $min; + } + } else { + if ($width < $min) { + $dest_width = $min; + $dest_height = intval(($height * $min) / $width); + } else { + if ($height < $min) { + $dest_width = intval(($width * $min) / $height); + $dest_height = $min; + } else { + $dest_width = $width; + $dest_height = $height; + } + } + } + + if ($this->is_imagick()) { + return $this->scaleImage($dest_width, $dest_height); + } + + $dest = imagecreatetruecolor($dest_width, $dest_height); + imagealphablending($dest, false); + imagesavealpha($dest, true); + if ($this->type=='image/png') { + imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha + } + imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dest_width, $dest_height, $width, $height); + if ($this->image) { + imagedestroy($this->image); + } + $this->image = $dest; + $this->width = imagesx($this->image); + $this->height = imagesy($this->image); + } + + + + public function scaleImageSquare($dim) { + if (!$this->is_valid()) { + return false; + } + + if ($this->is_imagick()) { + $this->image->setFirstIterator(); + do { + $this->image->scaleImage($dim, $dim); + } while ($this->image->nextImage()); + return; + } + + $dest = imagecreatetruecolor($dim, $dim); + imagealphablending($dest, false); + imagesavealpha($dest, true); + if ($this->type=='image/png') { + imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha + } + imagecopyresampled($dest, $this->image, 0, 0, 0, 0, $dim, $dim, $this->width, $this->height); + if ($this->image) { + imagedestroy($this->image); + } + $this->image = $dest; + $this->width = imagesx($this->image); + $this->height = imagesy($this->image); + } + + + public function cropImage($max, $x, $y, $w, $h) { + if (!$this->is_valid()) { + return false; + } + + if ($this->is_imagick()) { $this->image->setFirstIterator(); do { $this->image->cropImage($w, $h, $x, $y); - /** + /* * We need to remove the canva, * or the image is not resized to the crop: * http://php.net/manual/en/imagick.cropimage.php#97232 @@ -520,159 +549,167 @@ class Photo { return $this->scaleImage($max); } - $dest = imagecreatetruecolor( $max, $max ); - imagealphablending($dest, false); - imagesavealpha($dest, true); - if ($this->type=='image/png') imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha - imagecopyresampled($dest, $this->image, 0, 0, $x, $y, $max, $max, $w, $h); - if($this->image) - imagedestroy($this->image); - $this->image = $dest; - $this->width = imagesx($this->image); - $this->height = imagesy($this->image); - } - - public function saveImage($path) { - if(!$this->is_valid()) - return FALSE; - - $string = $this->imageString(); - - $a = get_app(); - - $stamp1 = microtime(true); - file_put_contents($path, $string); - $a->save_timestamp($stamp1, "file"); - } - - public function imageString() { - if(!$this->is_valid()) - return FALSE; - - if($this->is_imagick()) { - /* Clean it */ - $this->image = $this->image->deconstructImages(); - $string = $this->image->getImagesBlob(); - return $string; + $dest = imagecreatetruecolor($max, $max); + imagealphablending($dest, false); + imagesavealpha($dest, true); + if ($this->type=='image/png') { + imagefill($dest, 0, 0, imagecolorallocatealpha($dest, 0, 0, 0, 127)); // fill with alpha + } + imagecopyresampled($dest, $this->image, 0, 0, $x, $y, $max, $max, $w, $h); + if ($this->image) { + imagedestroy($this->image); + } + $this->image = $dest; + $this->width = imagesx($this->image); + $this->height = imagesy($this->image); } - $quality = FALSE; + public function saveImage($path) { + if (!$this->is_valid()) { + return false; + } - ob_start(); + $string = $this->imageString(); - // Enable interlacing - imageinterlace($this->image, true); + $a = get_app(); - switch($this->getType()){ - case "image/png": - $quality = get_config('system','png_quality'); - if((! $quality) || ($quality > 9)) - $quality = PNG_QUALITY; - imagepng($this->image,NULL, $quality); - break; - case "image/jpeg": - $quality = get_config('system','jpeg_quality'); - if((! $quality) || ($quality > 100)) - $quality = JPEG_QUALITY; - imagejpeg($this->image,NULL,$quality); + $stamp1 = microtime(true); + file_put_contents($path, $string); + $a->save_timestamp($stamp1, "file"); } - $string = ob_get_contents(); - ob_end_clean(); - return $string; - } + public function imageString() { + if (!$this->is_valid()) { + return false; + } + + if ($this->is_imagick()) { + /* Clean it */ + $this->image = $this->image->deconstructImages(); + $string = $this->image->getImagesBlob(); + return $string; + } + + $quality = false; + + ob_start(); + + // Enable interlacing + imageinterlace($this->image, true); + + switch($this->getType()){ + case "image/png": + $quality = get_config('system', 'png_quality'); + if ((!$quality) || ($quality > 9)) { + $quality = PNG_QUALITY; + } + imagepng($this->image, null, $quality); + break; + case "image/jpeg": + $quality = get_config('system', 'jpeg_quality'); + if ((!$quality) || ($quality > 100)) { + $quality = JPEG_QUALITY; + } + imagejpeg($this->image, null, $quality); + } + $string = ob_get_contents(); + ob_end_clean(); + + return $string; + } - public function store($uid, $cid, $rid, $filename, $album, $scale, $profile = 0, $allow_cid = '', $allow_gid = '', $deny_cid = '', $deny_gid = '') { + public function store($uid, $cid, $rid, $filename, $album, $scale, $profile = 0, $allow_cid = '', $allow_gid = '', $deny_cid = '', $deny_gid = '') { - $r = q("select `guid` from photo where `resource-id` = '%s' and `guid` != '' limit 1", - dbesc($rid) - ); - if(count($r)) - $guid = $r[0]['guid']; - else - $guid = get_guid(); + $r = q("SELECT `guid` FROM `photo` WHERE `resource-id` = '%s' AND `guid` != '' LIMIT 1", + dbesc($rid) + ); + if (dbm::is_result($r)) { + $guid = $r[0]['guid']; + } else { + $guid = get_guid(); + } - $x = q("select id from photo where `resource-id` = '%s' and uid = %d and `contact-id` = %d and `scale` = %d limit 1", - dbesc($rid), - intval($uid), - intval($cid), - intval($scale) - ); - if(count($x)) { - $r = q("UPDATE `photo` - set `uid` = %d, - `contact-id` = %d, - `guid` = '%s', - `resource-id` = '%s', - `created` = '%s', - `edited` = '%s', - `filename` = '%s', - `type` = '%s', - `album` = '%s', - `height` = %d, - `width` = %d, + $x = q("SELECT `id` FROM `photo` WHERE `resource-id` = '%s' AND `uid` = %d AND `contact-id` = %d AND `scale` = %d LIMIT 1", + dbesc($rid), + intval($uid), + intval($cid), + intval($scale) + ); + if (dbm::is_result($x)) { + $r = q("UPDATE `photo` + SET `uid` = %d, + `contact-id` = %d, + `guid` = '%s', + `resource-id` = '%s', + `created` = '%s', + `edited` = '%s', + `filename` = '%s', + `type` = '%s', + `album` = '%s', + `height` = %d, + `width` = %d, `datasize` = %d, - `data` = '%s', - `scale` = %d, - `profile` = %d, - `allow_cid` = '%s', - `allow_gid` = '%s', - `deny_cid` = '%s', - `deny_gid` = '%s' - where id = %d", + `data` = '%s', + `scale` = %d, + `profile` = %d, + `allow_cid` = '%s', + `allow_gid` = '%s', + `deny_cid` = '%s', + `deny_gid` = '%s' + WHERE `id` = %d", - intval($uid), - intval($cid), - dbesc($guid), - dbesc($rid), - dbesc(datetime_convert()), - dbesc(datetime_convert()), - dbesc(basename($filename)), - dbesc($this->getType()), - dbesc($album), - intval($this->getHeight()), - intval($this->getWidth()), + intval($uid), + intval($cid), + dbesc($guid), + dbesc($rid), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc(basename($filename)), + dbesc($this->getType()), + dbesc($album), + intval($this->getHeight()), + intval($this->getWidth()), dbesc(strlen($this->imageString())), - dbesc($this->imageString()), - intval($scale), - intval($profile), - dbesc($allow_cid), - dbesc($allow_gid), - dbesc($deny_cid), - dbesc($deny_gid), - intval($x[0]['id']) - ); - } - else { - $r = q("INSERT INTO `photo` - ( `uid`, `contact-id`, `guid`, `resource-id`, `created`, `edited`, `filename`, type, `album`, `height`, `width`, `datasize`, `data`, `scale`, `profile`, `allow_cid`, `allow_gid`, `deny_cid`, `deny_gid` ) - VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, '%s', %d, %d, '%s', '%s', '%s', '%s' )", - intval($uid), - intval($cid), - dbesc($guid), - dbesc($rid), - dbesc(datetime_convert()), - dbesc(datetime_convert()), - dbesc(basename($filename)), - dbesc($this->getType()), - dbesc($album), - intval($this->getHeight()), - intval($this->getWidth()), + dbesc($this->imageString()), + intval($scale), + intval($profile), + dbesc($allow_cid), + dbesc($allow_gid), + dbesc($deny_cid), + dbesc($deny_gid), + intval($x[0]['id']) + ); + } else { + $r = q("INSERT INTO `photo` + (`uid`, `contact-id`, `guid`, `resource-id`, `created`, `edited`, `filename`, type, `album`, `height`, `width`, `datasize`, `data`, `scale`, `profile`, `allow_cid`, `allow_gid`, `deny_cid`, `deny_gid`) + VALUES (%d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, '%s', %d, %d, '%s', '%s', '%s', '%s')", + intval($uid), + intval($cid), + dbesc($guid), + dbesc($rid), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc(basename($filename)), + dbesc($this->getType()), + dbesc($album), + intval($this->getHeight()), + intval($this->getWidth()), dbesc(strlen($this->imageString())), - dbesc($this->imageString()), - intval($scale), - intval($profile), - dbesc($allow_cid), - dbesc($allow_gid), - dbesc($deny_cid), - dbesc($deny_gid) - ); + dbesc($this->imageString()), + intval($scale), + intval($profile), + dbesc($allow_cid), + dbesc($allow_gid), + dbesc($deny_cid), + dbesc($deny_gid) + ); + } + + return $r; } - return $r; - } -}} +} /** @@ -682,41 +719,43 @@ class Photo { * @arg $fromcurl boolean Check Content-Type header from curl request */ function guess_image_type($filename, $fromcurl=false) { - logger('Photo: guess_image_type: '.$filename . ($fromcurl?' from curl headers':''), LOGGER_DEBUG); - $type = null; - if ($fromcurl) { - $a = get_app(); - $headers=array(); - $h = explode("\n",$a->get_curl_headers()); - foreach ($h as $l) { - list($k,$v) = array_map("trim", explode(":", trim($l), 2)); - $headers[$k] = $v; + logger('Photo: guess_image_type: '.$filename . ($fromcurl?' from curl headers':''), LOGGER_DEBUG); + $type = null; + if ($fromcurl) { + $a = get_app(); + $headers=array(); + $h = explode("\n",$a->get_curl_headers()); + foreach ($h as $l) { + list($k,$v) = array_map("trim", explode(":", trim($l), 2)); + $headers[$k] = $v; + } + if (array_key_exists('Content-Type', $headers)) + $type = $headers['Content-Type']; } - if (array_key_exists('Content-Type', $headers)) - $type = $headers['Content-Type']; - } - if (is_null($type)){ - // Guessing from extension? Isn't that... dangerous? - if(class_exists('Imagick') && file_exists($filename) && is_readable($filename)) { - /** - * Well, this not much better, - * but at least it comes from the data inside the image, - * we won't be tricked by a manipulated extension - */ - $image = new Imagick($filename); - $type = $image->getImageMimeType(); - $image->setInterlaceScheme(Imagick::INTERLACE_PLANE); - } else { - $ext = pathinfo($filename, PATHINFO_EXTENSION); - $types = Photo::supportedTypes(); - $type = "image/jpeg"; - foreach ($types as $m=>$e){ - if ($ext==$e) $type = $m; - } + if (is_null($type)){ + // Guessing from extension? Isn't that... dangerous? + if (class_exists('Imagick') && file_exists($filename) && is_readable($filename)) { + /** + * Well, this not much better, + * but at least it comes from the data inside the image, + * we won't be tricked by a manipulated extension + */ + $image = new Imagick($filename); + $type = $image->getImageMimeType(); + $image->setInterlaceScheme(Imagick::INTERLACE_PLANE); + } else { + $ext = pathinfo($filename, PATHINFO_EXTENSION); + $types = Photo::supportedTypes(); + $type = "image/jpeg"; + foreach ($types as $m => $e){ + if ($ext == $e) { + $type = $m; + } + } + } } - } - logger('Photo: guess_image_type: type='.$type, LOGGER_DEBUG); - return $type; + logger('Photo: guess_image_type: type='.$type, LOGGER_DEBUG); + return $type; } @@ -730,16 +769,17 @@ function guess_image_type($filename, $fromcurl=false) { * * @return array Returns array of the different avatar sizes */ -function update_contact_avatar($avatar,$uid,$cid, $force = false) { +function update_contact_avatar($avatar, $uid, $cid, $force = false) { $r = q("SELECT `avatar`, `photo`, `thumb`, `micro` FROM `contact` WHERE `id` = %d LIMIT 1", intval($cid)); - if (!$r) + if (!dbm::is_result($r)) { return false; - else + } else { $data = array($r[0]["photo"], $r[0]["thumb"], $r[0]["micro"]); + } if (($r[0]["avatar"] != $avatar) OR $force) { - $photos = import_profile_photo($avatar,$uid,$cid, true); + $photos = import_profile_photo($avatar, $uid, $cid, true); if ($photos) { q("UPDATE `contact` SET `avatar` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s', `avatar-date` = '%s' WHERE `id` = %d", @@ -752,66 +792,68 @@ function update_contact_avatar($avatar,$uid,$cid, $force = false) { return $data; } -function import_profile_photo($photo,$uid,$cid, $quit_on_error = false) { +function import_profile_photo($photo, $uid, $cid, $quit_on_error = false) { - $a = get_app(); - - $r = q("select `resource-id` from photo where `uid` = %d and `contact-id` = %d and `scale` = 4 and `album` = 'Contact Photos' limit 1", + $r = q("SELECT `resource-id` FROM `photo` WHERE `uid` = %d AND `contact-id` = %d AND `scale` = 4 AND `album` = 'Contact Photos' LIMIT 1", intval($uid), intval($cid) ); - if(count($r) && strlen($r[0]['resource-id'])) { + if (dbm::is_result($r) && strlen($r[0]['resource-id'])) { $hash = $r[0]['resource-id']; } else { $hash = photo_new_resource(); - } + } $photo_failure = false; $filename = basename($photo); - $img_str = fetch_url($photo,true); + $img_str = fetch_url($photo, true); - if ($quit_on_error AND ($img_str == "")) + if ($quit_on_error AND ($img_str == "")) { return false; + } - $type = guess_image_type($photo,true); + $type = guess_image_type($photo, true); $img = new Photo($img_str, $type); - if($img->is_valid()) { + if ($img->is_valid()) { $img->scaleImageSquare(175); - $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 4 ); + $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 4); - if($r === false) + if ($r === false) $photo_failure = true; $img->scaleImage(80); - $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 5 ); + $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 5); - if($r === false) + if ($r === false) $photo_failure = true; $img->scaleImage(48); - $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 6 ); + $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 6); - if($r === false) + if ($r === false) { $photo_failure = true; + } - $photo = $a->get_baseurl() . '/photo/' . $hash . '-4.' . $img->getExt(); - $thumb = $a->get_baseurl() . '/photo/' . $hash . '-5.' . $img->getExt(); - $micro = $a->get_baseurl() . '/photo/' . $hash . '-6.' . $img->getExt(); - } else + $photo = App::get_baseurl() . '/photo/' . $hash . '-4.' . $img->getExt(); + $thumb = App::get_baseurl() . '/photo/' . $hash . '-5.' . $img->getExt(); + $micro = App::get_baseurl() . '/photo/' . $hash . '-6.' . $img->getExt(); + } else { $photo_failure = true; + } - if($photo_failure AND $quit_on_error) + if ($photo_failure AND $quit_on_error) { return false; + } - if($photo_failure) { - $photo = $a->get_baseurl() . '/images/person-175.jpg'; - $thumb = $a->get_baseurl() . '/images/person-80.jpg'; - $micro = $a->get_baseurl() . '/images/person-48.jpg'; + if ($photo_failure) { + $photo = App::get_baseurl() . '/images/person-175.jpg'; + $thumb = App::get_baseurl() . '/images/person-80.jpg'; + $micro = App::get_baseurl() . '/images/person-48.jpg'; } return(array($photo,$thumb,$micro)); @@ -823,14 +865,13 @@ function get_photo_info($url) { $data = Cache::get($url); - if (is_null($data)) { + if (is_null($data) OR !$data OR !is_array($data)) { $img_str = fetch_url($url, true, $redirects, 4); - $filesize = strlen($img_str); - if (function_exists("getimagesizefromstring")) + if (function_exists("getimagesizefromstring")) { $data = getimagesizefromstring($img_str); - else { + } else { $tempfile = tempnam(get_temppath(), "cache"); $a = get_app(); @@ -842,12 +883,12 @@ function get_photo_info($url) { unlink($tempfile); } - if ($data) + if ($data) { $data["size"] = $filesize; + } - Cache::set($url, serialize($data)); - } else - $data = unserialize($data); + Cache::set($url, $data); + } return $data; } @@ -856,40 +897,41 @@ function scale_image($width, $height, $max) { $dest_width = $dest_height = 0; - if((!$width) || (!$height)) - return FALSE; + if ((!$width) || (!$height)) { + return false; + } - if($width > $max && $height > $max) { + if ($width > $max && $height > $max) { // very tall image (greater than 16:9) // constrain the width - let the height float. - if((($height * 9) / 16) > $width) { + if ((($height * 9) / 16) > $width) { $dest_width = $max; - $dest_height = intval(( $height * $max ) / $width); - } elseif($width > $height) { + $dest_height = intval(($height * $max) / $width); + } elseif ($width > $height) { // else constrain both dimensions $dest_width = $max; - $dest_height = intval(( $height * $max ) / $width); - } else { - $dest_width = intval(( $width * $max ) / $height); + $dest_height = intval(($height * $max) / $width); + } else { + $dest_width = intval(($width * $max) / $height); $dest_height = $max; } } else { - if( $width > $max ) { + if ($width > $max) { $dest_width = $max; - $dest_height = intval(( $height * $max ) / $width); - } else { - if( $height > $max ) { + $dest_height = intval(($height * $max) / $width); + } else { + if ($height > $max) { // very tall image (greater than 16:9) // but width is OK - don't do anything - if((($height * 9) / 16) > $width) { + if ((($height * 9) / 16) > $width) { $dest_width = $width; $dest_height = $height; } else { - $dest_width = intval(( $width * $max ) / $height); + $dest_width = intval(($width * $max) / $height); $dest_height = $max; } } else { @@ -901,12 +943,12 @@ function scale_image($width, $height, $max) { return array("width" => $dest_width, "height" => $dest_height); } -function store_photo($a, $uid, $imagedata = "", $url = "") { +function store_photo(App $a, $uid, $imagedata = "", $url = "") { $r = q("SELECT `user`.`nickname`, `user`.`page-flags`, `contact`.`id` FROM `user` INNER JOIN `contact` on `user`.`uid` = `contact`.`uid` - WHERE `user`.`uid` = %d AND `user`.`blocked` = 0 and `contact`.`self` = 1 LIMIT 1", + WHERE `user`.`uid` = %d AND `user`.`blocked` = 0 AND `contact`.`self` = 1 LIMIT 1", intval($uid)); - if(!count($r)) { + if (!dbm::is_result($r)) { logger("Can't detect user data for uid ".$uid, LOGGER_DEBUG); return(array()); } @@ -928,24 +970,24 @@ function store_photo($a, $uid, $imagedata = "", $url = "") { $a->save_timestamp($stamp1, "file"); } - $maximagesize = get_config('system','maximagesize'); + $maximagesize = get_config('system', 'maximagesize'); - if(($maximagesize) && (strlen($imagedata) > $maximagesize)) { + if (($maximagesize) && (strlen($imagedata) > $maximagesize)) { logger("Image exceeds size limit of ".$maximagesize, LOGGER_DEBUG); return(array()); - } + } /* - $r = q("select sum(octet_length(data)) as total from photo where uid = %d and scale = 0 and album != 'Contact Photos' ", - intval($uid) - ); + $r = q("select sum(octet_length(data)) as total from photo where uid = %d and scale = 0 and album != 'Contact Photos' ", + intval($uid) + ); - $limit = service_class_fetch($uid,'photo_upload_limit'); + $limit = service_class_fetch($uid,'photo_upload_limit'); - if(($limit !== false) && (($r[0]['total'] + strlen($imagedata)) > $limit)) { + if (($limit !== false) && (($r[0]['total'] + strlen($imagedata)) > $limit)) { logger("Image exceeds personal limit of uid ".$uid, LOGGER_DEBUG); return(array()); - } + } */ $tempfile = tempnam(get_temppath(), "cache"); @@ -964,7 +1006,7 @@ function store_photo($a, $uid, $imagedata = "", $url = "") { $ph = new Photo($imagedata, $data["mime"]); - if(!$ph->is_valid()) { + if (!$ph->is_valid()) { unlink($tempfile); logger("Picture is no valid picture", LOGGER_DEBUG); return(array()); @@ -973,11 +1015,13 @@ function store_photo($a, $uid, $imagedata = "", $url = "") { $ph->orient($tempfile); unlink($tempfile); - $max_length = get_config('system','max_image_length'); - if(! $max_length) + $max_length = get_config('system', 'max_image_length'); + if (! $max_length) { $max_length = MAX_IMAGE_LENGTH; - if($max_length > 0) + } + if ($max_length > 0) { $ph->scaleImage($max_length); + } $width = $ph->getWidth(); $height = $ph->getHeight(); @@ -989,55 +1033,61 @@ function store_photo($a, $uid, $imagedata = "", $url = "") { // Pictures are always public by now //$defperm = '<'.$default_cid.'>'; $defperm = ""; - $visitor = 0; + $visitor = 0; $r = $ph->store($uid, $visitor, $hash, $tempfile, t('Wall Photos'), 0, 0, $defperm); - if(!$r) { + if (!$r) { logger("Picture couldn't be stored", LOGGER_DEBUG); return(array()); } - $image = array("page" => $a->get_baseurl().'/photos/'.$page_owner_nick.'/image/'.$hash, - "full" => $a->get_baseurl()."/photo/{$hash}-0.".$ph->getExt()); + $image = array("page" => App::get_baseurl().'/photos/'.$page_owner_nick.'/image/'.$hash, + "full" => App::get_baseurl()."/photo/{$hash}-0.".$ph->getExt()); - if($width > 800 || $height > 800) - $image["large"] = $a->get_baseurl()."/photo/{$hash}-0.".$ph->getExt(); + if ($width > 800 || $height > 800) { + $image["large"] = App::get_baseurl()."/photo/{$hash}-0.".$ph->getExt(); + } - if($width > 640 || $height > 640) { + if ($width > 640 || $height > 640) { $ph->scaleImage(640); $r = $ph->store($uid, $visitor, $hash, $tempfile, t('Wall Photos'), 1, 0, $defperm); - if($r) - $image["medium"] = $a->get_baseurl()."/photo/{$hash}-1.".$ph->getExt(); + if ($r) { + $image["medium"] = App::get_baseurl()."/photo/{$hash}-1.".$ph->getExt(); + } } - if($width > 320 || $height > 320) { + if ($width > 320 || $height > 320) { $ph->scaleImage(320); $r = $ph->store($uid, $visitor, $hash, $tempfile, t('Wall Photos'), 2, 0, $defperm); - if($r) - $image["small"] = $a->get_baseurl()."/photo/{$hash}-2.".$ph->getExt(); + if ($r) { + $image["small"] = App::get_baseurl()."/photo/{$hash}-2.".$ph->getExt(); + } } - if($width > 160 AND $height > 160) { + if ($width > 160 AND $height > 160) { $x = 0; $y = 0; $min = $ph->getWidth(); - if ($min > 160) + if ($min > 160) { $x = ($min - 160) / 2; + } if ($ph->getHeight() < $min) { $min = $ph->getHeight(); - if ($min > 160) + if ($min > 160) { $y = ($min - 160) / 2; + } } $min = 160; $ph->cropImage(160, $x, $y, $min, $min); $r = $ph->store($uid, $visitor, $hash, $tempfile, t('Wall Photos'), 3, 0, $defperm); - if($r) - $image["thumb"] = $a->get_baseurl()."/photo/{$hash}-3.".$ph->getExt(); + if ($r) { + $image["thumb"] = App::get_baseurl()."/photo/{$hash}-3.".$ph->getExt(); + } } // Set the full image as preview image. This will be overwritten, if the picture is larger than 640. @@ -1051,9 +1101,9 @@ function store_photo($a, $uid, $imagedata = "", $url = "") { //if (isset($image["small"])) // $image["preview"] = $image["small"]; - if (isset($image["medium"])) + if (isset($image["medium"])) { $image["preview"] = $image["medium"]; + } return($image); } - diff --git a/include/Probe.php b/include/Probe.php new file mode 100644 index 0000000000..1b6feb107f --- /dev/null +++ b/include/Probe.php @@ -0,0 +1,1187 @@ + Link to LRDD endpoint + * 'lrdd-xml' => Link to LRDD endpoint in XML format + * 'lrdd-json' => Link to LRDD endpoint in JSON format + */ + private function xrd($host) { + + $ssl_url = "https://".$host."/.well-known/host-meta"; + $url = "http://".$host."/.well-known/host-meta"; + + $xrd_timeout = Config::get('system','xrd_timeout', 20); + $redirects = 0; + + $ret = z_fetch_url($ssl_url, false, $redirects, array('timeout' => $xrd_timeout, 'accept_content' => 'application/xrd+xml')); + if ($ret['errno'] == CURLE_OPERATION_TIMEDOUT) { + return false; + } + $xml = $ret['body']; + + $xrd = parse_xml_string($xml, false); + + if (!is_object($xrd)) { + $ret = z_fetch_url($url, false, $redirects, array('timeout' => $xrd_timeout, 'accept_content' => 'application/xrd+xml')); + if ($ret['errno'] == CURLE_OPERATION_TIMEDOUT) { + return false; + } + $xml = $ret['body']; + $xrd = parse_xml_string($xml, false); + } + if (!is_object($xrd)) + return false; + + $links = xml::element_to_array($xrd); + if (!isset($links["xrd"]["link"])) + return false; + + $xrd_data = array(); + + foreach ($links["xrd"]["link"] AS $value => $link) { + if (isset($link["@attributes"])) + $attributes = $link["@attributes"]; + elseif ($value == "@attributes") + $attributes = $link; + else + continue; + + if (($attributes["rel"] == "lrdd") AND + ($attributes["type"] == "application/xrd+xml")) + $xrd_data["lrdd-xml"] = $attributes["template"]; + elseif (($attributes["rel"] == "lrdd") AND + ($attributes["type"] == "application/json")) + $xrd_data["lrdd-json"] = $attributes["template"]; + elseif ($attributes["rel"] == "lrdd") + $xrd_data["lrdd"] = $attributes["template"]; + } + return $xrd_data; + } + + /** + * @brief Perform Webfinger lookup and return DFRN data + * + * Given an email style address, perform webfinger lookup and + * return the resulting DFRN profile URL, or if no DFRN profile URL + * is located, returns an OStatus subscription template (prefixed + * with the string 'stat:' to identify it as on OStatus template). + * If this isn't an email style address just return $webbie. + * Return an empty string if email-style addresses but webfinger fails, + * or if the resultant personal XRD doesn't contain a supported + * subscription/friend-request attribute. + * + * amended 7/9/2011 to return an hcard which could save potentially loading + * a lengthy content page to scrape dfrn attributes + * + * @param string $webbie Address that should be probed + * @param string $hcard Link to the hcard - is returned by reference + * + * @return string profile link + */ + + public static function webfinger_dfrn($webbie, &$hcard) { + + $profile_link = ''; + + $links = self::lrdd($webbie); + logger('webfinger_dfrn: '.$webbie.':'.print_r($links,true), LOGGER_DATA); + if (count($links)) { + foreach ($links as $link) { + if ($link['@attributes']['rel'] === NAMESPACE_DFRN) + $profile_link = $link['@attributes']['href']; + if (($link['@attributes']['rel'] === NAMESPACE_OSTATUSSUB) AND ($profile_link == "")) + $profile_link = 'stat:'.$link['@attributes']['template']; + if ($link['@attributes']['rel'] === 'http://microformats.org/profile/hcard') + $hcard = $link['@attributes']['href']; + } + } + return $profile_link; + } + + /** + * @brief Check an URI for LRDD data + * + * this is a replacement for the "lrdd" function in include/network.php. + * It isn't used in this class and has some redundancies in the code. + * When time comes we can check the existing calls for "lrdd" if we can rework them. + * + * @param string $uri Address that should be probed + * + * @return array uri data + */ + public static function lrdd($uri) { + + $lrdd = self::xrd($uri); + + if (!$lrdd) { + $parts = @parse_url($uri); + if (!$parts) + return array(); + + $host = $parts["host"]; + + $path_parts = explode("/", trim($parts["path"], "/")); + + do { + $lrdd = self::xrd($host); + $host .= "/".array_shift($path_parts); + } while (!$lrdd AND (sizeof($path_parts) > 0)); + } + + if (!$lrdd) + return array(); + + foreach ($lrdd AS $key => $link) { + if ($webfinger) + continue; + + if (!in_array($key, array("lrdd", "lrdd-xml", "lrdd-json"))) + continue; + + $path = str_replace('{uri}', urlencode($uri), $link); + $webfinger = self::webfinger($path); + + if (!$webfinger AND (strstr($uri, "@"))) { + $path = str_replace('{uri}', urlencode("acct:".$uri), $link); + $webfinger = self::webfinger($path); + } + } + + if (!is_array($webfinger["links"])) + return false; + + $data = array(); + + foreach ($webfinger["links"] AS $link) + $data[] = array("@attributes" => $link); + + if (is_array($webfinger["aliases"])) + foreach ($webfinger["aliases"] AS $alias) + $data[] = array("@attributes" => + array("rel" => "alias", + "href" => $alias)); + + return $data; + } + + /** + * @brief Fetch information (protocol endpoints and user information) about a given uri + * + * @param string $uri Address that should be probed + * @param string $network Test for this specific network + * @param integer $uid User ID for the probe (only used for mails) + * @param boolean $cache Use cached values? + * + * @return array uri data + */ + public static function uri($uri, $network = "", $uid = 0, $cache = true) { + + if ($cache) { + $result = Cache::get("probe_url:".$network.":".$uri); + if (!is_null($result)) { + return $result; + } + } + + if ($uid == 0) + $uid = local_user(); + + $data = self::detect($uri, $network, $uid); + + if (!isset($data["url"])) + $data["url"] = $uri; + + if ($data["photo"] != "") + $data["baseurl"] = matching_url(normalise_link($data["baseurl"]), normalise_link($data["photo"])); + else + $data["photo"] = App::get_baseurl().'/images/person-175.jpg'; + + if (!isset($data["name"]) OR ($data["name"] == "")) { + if (isset($data["nick"])) + $data["name"] = $data["nick"]; + + if ($data["name"] == "") + $data["name"] = $data["url"]; + } + + if (!isset($data["nick"]) OR ($data["nick"] == "")) { + $data["nick"] = strtolower($data["name"]); + + if (strpos($data['nick'], ' ')) + $data['nick'] = trim(substr($data['nick'], 0, strpos($data['nick'], ' '))); + } + + if (!isset($data["network"])) + $data["network"] = NETWORK_PHANTOM; + + $data = self::rearrange_data($data); + + // Only store into the cache if the value seems to be valid + if (!in_array($data['network'], array(NETWORK_PHANTOM, NETWORK_MAIL))) { + Cache::set("probe_url:".$network.":".$uri, $data, CACHE_DAY); + + /// @todo temporary fix - we need a real contact update function that updates only changing fields + /// The biggest problem is the avatar picture that could have a reduced image size. + /// It should only be updated if the existing picture isn't existing anymore. + if (($data['network'] != NETWORK_FEED) AND ($mode == PROBE_NORMAL) AND + $data["name"] AND $data["nick"] AND $data["url"] AND $data["addr"] AND $data["poll"]) + q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `url` = '%s', `addr` = '%s', + `notify` = '%s', `poll` = '%s', `alias` = '%s', `success_update` = '%s' + WHERE `nurl` = '%s' AND NOT `self` AND `uid` = 0", + dbesc($data["name"]), + dbesc($data["nick"]), + dbesc($data["url"]), + dbesc($data["addr"]), + dbesc($data["notify"]), + dbesc($data["poll"]), + dbesc($data["alias"]), + dbesc(datetime_convert()), + dbesc(normalise_link($data['url'])) + ); + } + return $data; + } + + /** + * @brief Fetch information (protocol endpoints and user information) about a given uri + * + * This function is only called by the "uri" function that adds caching and rearranging of data. + * + * @param string $uri Address that should be probed + * @param string $network Test for this specific network + * @param integer $uid User ID for the probe (only used for mails) + * + * @return array uri data + */ + private function detect($uri, $network, $uid) { + if (strstr($uri, '@')) { + // If the URI starts with "mailto:" then jump directly to the mail detection + if (strpos($url,'mailto:') !== false) { + $uri = str_replace('mailto:', '', $url); + return self::mail($uri, $uid); + } + + if ($network == NETWORK_MAIL) + return self::mail($uri, $uid); + + // Remove "acct:" from the URI + $uri = str_replace('acct:', '', $uri); + + $host = substr($uri,strpos($uri, '@') + 1); + $nick = substr($uri,0, strpos($uri, '@')); + + if (strpos($uri, '@twitter.com')) + return array("network" => NETWORK_TWITTER); + + $lrdd = self::xrd($host); + + if (!$lrdd) + return self::mail($uri, $uid); + + $addr = $uri; + } else { + $parts = parse_url($uri); + if (!isset($parts["scheme"]) OR + !isset($parts["host"]) OR + !isset($parts["path"])) + return false; + + /// @todo: Ports? + $host = $parts["host"]; + + if ($host == 'twitter.com') + return array("network" => NETWORK_TWITTER); + + $lrdd = self::xrd($host); + + $path_parts = explode("/", trim($parts["path"], "/")); + + while (!$lrdd AND (sizeof($path_parts) > 1)) { + $host .= "/".array_shift($path_parts); + $lrdd = self::xrd($host); + } + if (!$lrdd) + return self::feed($uri); + + $nick = array_pop($path_parts); + $addr = $nick."@".$host; + } + $webfinger = false; + + /// @todo Do we need the prefix "acct:" or "acct://"? + + foreach ($lrdd AS $key => $link) { + if ($webfinger) + continue; + + if (!in_array($key, array("lrdd", "lrdd-xml", "lrdd-json"))) + continue; + + // Try webfinger with the address (user@domain.tld) + $path = str_replace('{uri}', urlencode($addr), $link); + $webfinger = self::webfinger($path); + + // Mastodon needs to have it with "acct:" + if (!$webfinger) { + $path = str_replace('{uri}', urlencode("acct:".$addr), $link); + $webfinger = self::webfinger($path); + } + + // If webfinger wasn't successful then try it with the URL - possibly in the format https://... + if (!$webfinger AND ($uri != $addr)) { + $path = str_replace('{uri}', urlencode($uri), $link); + $webfinger = self::webfinger($path); + + // Since the detection with the address wasn't successful, we delete it. + if ($webfinger) { + $nick = ""; + $addr = ""; + } + } + + } + if (!$webfinger) + return self::feed($uri); + + $result = false; + + logger("Probing ".$uri, LOGGER_DEBUG); + + if (in_array($network, array("", NETWORK_DFRN))) + $result = self::dfrn($webfinger); + if ((!$result AND ($network == "")) OR ($network == NETWORK_DIASPORA)) + $result = self::diaspora($webfinger); + if ((!$result AND ($network == "")) OR ($network == NETWORK_OSTATUS)) + $result = self::ostatus($webfinger); + if ((!$result AND ($network == "")) OR ($network == NETWORK_PUMPIO)) + $result = self::pumpio($webfinger); + if ((!$result AND ($network == "")) OR ($network == NETWORK_FEED)) + $result = self::feed($uri); + else { + // We overwrite the detected nick with our try if the previois routines hadn't detected it. + // Additionally it is overwritten when the nickname doesn't make sense (contains spaces). + if ((!isset($result["nick"]) OR ($result["nick"] == "") OR (strstr($result["nick"], " "))) AND ($nick != "")) + $result["nick"] = $nick; + + if ((!isset($result["addr"]) OR ($result["addr"] == "")) AND ($addr != "")) + $result["addr"] = $addr; + } + + logger($uri." is ".$result["network"], LOGGER_DEBUG); + + if (!isset($result["baseurl"]) OR ($result["baseurl"] == "")) { + $pos = strpos($result["url"], $host); + if ($pos) + $result["baseurl"] = substr($result["url"], 0, $pos).$host; + } + + return $result; + } + + /** + * @brief Perform a webfinger request. + * + * For details see RFC 7033:(.*?)<\/pre>/ism','self::encode',$s);
+ $s = preg_replace_callback('/(.*?)<\/code>/ism','self::encode',$s);
+
+ $params = self::get_list();
+ $params['string'] = $s;
+
+ if($sample) {
+ $s = '';
+ for($x = 0; $x < count($params['texts']); $x ++) {
+ $s .= '- ' . $params['texts'][$x] . '
- ' . $params['icons'][$x] . '
';
+ }
+ }
+ else {
+ $params['string'] = preg_replace_callback('/<(3+)/','self::preg_heart',$params['string']);
+ $s = str_replace($params['texts'],$params['icons'],$params['string']);
+ }
+
+ $s = preg_replace_callback('/(.*?)<\/pre>/ism','self::decode',$s);
+ $s = preg_replace_callback('/(.*?)<\/code>/ism','self::decode',$s);
+
+ return $s;
+ }
+
+ private function encode($m) {
+ return(str_replace($m[1],base64url_encode($m[1]),$m[0]));
+ }
+
+ private function decode($m) {
+ return(str_replace($m[1],base64url_decode($m[1]),$m[0]));
+ }
+
+
+ /**
+ * @brief expand <3333 to the correct number of hearts
+ *
+ * @param string $x
+ * @return string HTML Output
+ *
+ * @todo: Rework because it doesn't work correctly
+ */
+ private function preg_heart($x) {
+ if(strlen($x[1]) == 1)
+ return $x[0];
+ $t = '';
+ for($cnt = 0; $cnt < strlen($x[1]); $cnt ++)
+ $t .= '';
+ $r = str_replace($x[0],$t,$x[0]);
+ return $r;
+ }
+
+}
diff --git a/include/acl_selectors.php b/include/acl_selectors.php
index 69181b7359..f4b644d68f 100644
--- a/include/acl_selectors.php
+++ b/include/acl_selectors.php
@@ -1,13 +1,15 @@
module . '_pre_' . $selname, $arr);
- if(count($r)) {
- foreach($r as $rr) {
+ if (dbm::is_result($r)) {
+ foreach ($r as $rr) {
if((is_array($preselected)) && in_array($rr['id'], $preselected))
$selected = " selected=\"selected\" ";
else
@@ -63,20 +65,24 @@ function contact_selector($selname, $selclass, $preselected = false, $options) {
$exclude = false;
$size = 4;
- if(is_array($options)) {
- if(x($options,'size'))
+ if (is_array($options)) {
+ if (x($options,'size'))
$size = $options['size'];
- if(x($options,'mutual_friends'))
+ if (x($options,'mutual_friends')) {
$mutual = true;
- if(x($options,'single'))
+ }
+ if (x($options,'single')) {
$single = true;
- if(x($options,'multiple'))
+ }
+ if (x($options,'multiple')) {
$single = false;
- if(x($options,'exclude'))
+ }
+ if (x($options,'exclude')) {
$exclude = $options['exclude'];
+ }
- if(x($options,'networks')) {
+ if (x($options,'networks')) {
switch($options['networks']) {
case 'DFRN_ONLY':
$networks = array(NETWORK_DFRN);
@@ -129,7 +135,7 @@ function contact_selector($selname, $selclass, $preselected = false, $options) {
$o .= "