Merge pull request #5794 from annando/ap1

ActivityPub support
This commit is contained in:
Hypolite Petovan 2018-10-02 11:24:04 -04:00 committed by GitHub
commit 505350c9fb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
54 changed files with 4014 additions and 1065 deletions

View file

@ -41,7 +41,7 @@ define('FRIENDICA_PLATFORM', 'Friendica');
define('FRIENDICA_CODENAME', 'The Tazmans Flax-lily'); define('FRIENDICA_CODENAME', 'The Tazmans Flax-lily');
define('FRIENDICA_VERSION', '2018.12-dev'); define('FRIENDICA_VERSION', '2018.12-dev');
define('DFRN_PROTOCOL_VERSION', '2.23'); define('DFRN_PROTOCOL_VERSION', '2.23');
define('DB_UPDATE_VERSION', 1283); define('DB_UPDATE_VERSION', 1284);
define('NEW_UPDATE_ROUTINE_VERSION', 1170); define('NEW_UPDATE_ROUTINE_VERSION', 1170);
/** /**

View file

@ -37,12 +37,13 @@
"npm-asset/jgrowl": "^1.4", "npm-asset/jgrowl": "^1.4",
"npm-asset/fullcalendar": "^3.0.1", "npm-asset/fullcalendar": "^3.0.1",
"npm-asset/cropperjs": "1.2.2", "npm-asset/cropperjs": "1.2.2",
"npm-asset/imagesloaded": "4.1.4" "npm-asset/imagesloaded": "4.1.4",
"friendica/json-ld": "^1.0"
}, },
"repositories": [ "repositories": [
{ {
"type": "vcs", "type": "vcs",
"url": "https://github.com/pear/Text_Highlighter" "url": "https://git.friendi.ca/friendica/php-json-ld"
} }
], ],
"autoload": { "autoload": {

92
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "5f6a43237dc52758484cd21cd76e8ce6", "content-hash": "ece71ff50417ed5fcc1a439ea554925c",
"packages": [ "packages": [
{ {
"name": "asika/simple-console", "name": "asika/simple-console",
@ -241,6 +241,50 @@
], ],
"time": "2015-08-05T01:03:42+00:00" "time": "2015-08-05T01:03:42+00:00"
}, },
{
"name": "friendica/json-ld",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://git.friendi.ca/friendica/php-json-ld",
"reference": "a9ac64daf01cfd97e80c36a5104247d37c0ae5ef"
},
"require": {
"ext-json": "*",
"php": ">=5.4.0"
},
"type": "library",
"autoload": {
"files": [
"jsonld.php"
]
},
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Digital Bazaar, Inc.",
"email": "support@digitalbazaar.com",
"url": "http://digitalbazaar.com/"
},
{
"name": "Friendica Team",
"url": "https://friendi.ca/"
}
],
"description": "A JSON-LD Processor and API implementation in PHP.",
"homepage": "https://git.friendi.ca/friendica/php-json-ld",
"keywords": [
"JSON",
"JSON-LD",
"Linked Data",
"RDF",
"Semantic Web",
"jsonld"
],
"time": "2018-09-28T00:01:12+00:00"
},
{ {
"name": "fxp/composer-asset-plugin", "name": "fxp/composer-asset-plugin",
"version": "v1.4.2", "version": "v1.4.2",
@ -2000,6 +2044,52 @@
} }
], ],
"packages-dev": [ "packages-dev": [
{
"name": "digitalbazaar/json-ld",
"version": "0.4.7",
"source": {
"type": "git",
"url": "https://github.com/digitalbazaar/php-json-ld.git",
"reference": "dc1bd23f0ee2efd27ccf636d32d2738dabcee182"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/digitalbazaar/php-json-ld/zipball/dc1bd23f0ee2efd27ccf636d32d2738dabcee182",
"reference": "dc1bd23f0ee2efd27ccf636d32d2738dabcee182",
"shasum": ""
},
"require": {
"ext-json": "*",
"php": ">=5.3.0"
},
"type": "library",
"autoload": {
"files": [
"jsonld.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Digital Bazaar, Inc.",
"email": "support@digitalbazaar.com"
}
],
"description": "A JSON-LD Processor and API implementation in PHP.",
"homepage": "https://github.com/digitalbazaar/php-json-ld",
"keywords": [
"JSON-LD",
"Linked Data",
"RDF",
"Semantic Web",
"json",
"jsonld"
],
"time": "2016-04-25T04:17:52+00:00"
},
{ {
"name": "doctrine/instantiator", "name": "doctrine/instantiator",
"version": "1.0.5", "version": "1.0.5",

View file

@ -15,6 +15,34 @@
"name": ["UNIQUE", "name"] "name": ["UNIQUE", "name"]
} }
}, },
"apcontact": {
"comment": "ActivityPub compatible contacts - used in the ActivityPub implementation",
"fields": {
"url": {"type": "varbinary(255)", "not null": "1", "primary": "1", "comment": "URL of the contact"},
"uuid": {"type": "varchar(255)", "comment": ""},
"type": {"type": "varchar(20)", "not null": "1", "comment": ""},
"following": {"type": "varchar(255)", "comment": ""},
"followers": {"type": "varchar(255)", "comment": ""},
"inbox": {"type": "varchar(255)", "not null": "1", "comment": ""},
"outbox": {"type": "varchar(255)", "comment": ""},
"sharedinbox": {"type": "varchar(255)", "comment": ""},
"nick": {"type": "varchar(255)", "not null": "1", "default": "", "comment": ""},
"name": {"type": "varchar(255)", "comment": ""},
"about": {"type": "text", "comment": ""},
"photo": {"type": "varchar(255)", "comment": ""},
"addr": {"type": "varchar(255)", "comment": ""},
"alias": {"type": "varchar(255)", "comment": ""},
"pubkey": {"type": "text", "comment": ""},
"baseurl": {"type": "varchar(255)", "comment": "baseurl of the ap contact"},
"updated": {"type": "datetime", "not null": "1", "default": "0001-01-01 00:00:00", "comment": ""}
},
"indexes": {
"PRIMARY": ["url"],
"addr": ["addr(32)"],
"url": ["followers(190)"]
}
},
"attach": { "attach": {
"comment": "file attachments", "comment": "file attachments",
"fields": { "fields": {
@ -215,7 +243,7 @@
"reply-to-uri": {"type": "varbinary(255)", "not null": "1", "default": "", "comment": "URI to which this item is a reply"}, "reply-to-uri": {"type": "varbinary(255)", "not null": "1", "default": "", "comment": "URI to which this item is a reply"},
"conversation-uri": {"type": "varbinary(255)", "not null": "1", "default": "", "comment": "GNU Social conversation URI"}, "conversation-uri": {"type": "varbinary(255)", "not null": "1", "default": "", "comment": "GNU Social conversation URI"},
"conversation-href": {"type": "varbinary(255)", "not null": "1", "default": "", "comment": "GNU Social conversation link"}, "conversation-href": {"type": "varbinary(255)", "not null": "1", "default": "", "comment": "GNU Social conversation link"},
"protocol": {"type": "tinyint unsigned", "not null": "1", "default": "0", "comment": "The protocol of the item"}, "protocol": {"type": "tinyint unsigned", "not null": "1", "default": "255", "comment": "The protocol of the item"},
"source": {"type": "mediumtext", "comment": "Original source"}, "source": {"type": "mediumtext", "comment": "Original source"},
"received": {"type": "datetime", "not null": "1", "default": "0001-01-01 00:00:00", "comment": "Receiving date"} "received": {"type": "datetime", "not null": "1", "default": "0001-01-01 00:00:00", "comment": "Receiving date"}
}, },

View file

@ -4612,7 +4612,7 @@ function post_photo_item($hash, $allow_cid, $deny_cid, $allow_gid, $deny_gid, $f
$owner_record = DBA::selectFirst('contact', [], ['uid' => api_user(), 'self' => true]); $owner_record = DBA::selectFirst('contact', [], ['uid' => api_user(), 'self' => true]);
$arr = []; $arr = [];
$arr['guid'] = System::createGUID(32); $arr['guid'] = System::createUUID();
$arr['uid'] = intval(api_user()); $arr['uid'] = intval(api_user());
$arr['uri'] = $uri; $arr['uri'] = $uri;
$arr['parent-uri'] = $uri; $arr['parent-uri'] = $uri;

View file

@ -556,7 +556,7 @@ function conversation(App $a, array $items, $mode, $update, $preview = false, $o
if (in_array($mode, ['community', 'contacts'])) { if (in_array($mode, ['community', 'contacts'])) {
$writable = true; $writable = true;
} else { } else {
$writable = ($items[0]['uid'] == 0) && in_array($items[0]['network'], [Protocol::OSTATUS, Protocol::DIASPORA, Protocol::DFRN]); $writable = ($items[0]['uid'] == 0) && in_array($items[0]['network'], [Protocol::ACTIVITYPUB, Protocol::OSTATUS, Protocol::DIASPORA, Protocol::DFRN]);
} }
if (!local_user()) { if (!local_user()) {
@ -807,7 +807,7 @@ function conversation_add_children(array $parents, $block_authors, $order, $uid)
foreach ($items as $index => $item) { foreach ($items as $index => $item) {
if ($item['uid'] == 0) { if ($item['uid'] == 0) {
$items[$index]['writable'] = in_array($item['network'], [Protocol::OSTATUS, Protocol::DIASPORA, Protocol::DFRN]); $items[$index]['writable'] = in_array($item['network'], [Protocol::ACTIVITYPUB, Protocol::OSTATUS, Protocol::DIASPORA, Protocol::DFRN]);
} }
} }
@ -877,7 +877,7 @@ function item_photo_menu($item) {
} }
if ((($cid == 0) || ($rel == Contact::FOLLOWER)) && if ((($cid == 0) || ($rel == Contact::FOLLOWER)) &&
in_array($item['network'], [Protocol::DFRN, Protocol::OSTATUS, Protocol::DIASPORA])) { in_array($item['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::DIASPORA])) {
$menu[L10n::t('Connect/Follow')] = 'follow?url=' . urlencode($item['author-link']); $menu[L10n::t('Connect/Follow')] = 'follow?url=' . urlencode($item['author-link']);
} }
} else { } else {

View file

@ -332,15 +332,21 @@ if ($a->module_loaded) {
$a->page['page_title'] = $a->module; $a->page['page_title'] = $a->module;
$placeholder = ''; $placeholder = '';
if ($a->module_class) {
Addon::callHooks($a->module . '_mod_init', $placeholder); Addon::callHooks($a->module . '_mod_init', $placeholder);
if ($a->module_class) {
call_user_func([$a->module_class, 'init']); call_user_func([$a->module_class, 'init']);
} else if (function_exists($a->module . '_init')) { } else if (function_exists($a->module . '_init')) {
Addon::callHooks($a->module . '_mod_init', $placeholder);
$func = $a->module . '_init'; $func = $a->module . '_init';
$func($a); $func($a);
} }
// "rawContent" is especially meant for technical endpoints.
// This endpoint doesn't need any theme initialization or other comparable stuff.
if (!$a->error && $a->module_class) {
call_user_func([$a->module_class, 'rawContent']);
}
if (function_exists(str_replace('-', '_', $a->getCurrentTheme()) . '_init')) { if (function_exists(str_replace('-', '_', $a->getCurrentTheme()) . '_init')) {
$func = str_replace('-', '_', $a->getCurrentTheme()) . '_init'; $func = str_replace('-', '_', $a->getCurrentTheme()) . '_init';
$func($a); $func($a);

View file

@ -1488,7 +1488,7 @@ function admin_page_site(App $a)
'$community_page_style' => ['community_page_style', L10n::t("Community pages for visitors"), Config::get('system','community_page_style'), L10n::t("Which community pages should be available for visitors. Local users always see both pages."), $community_page_style_choices], '$community_page_style' => ['community_page_style', L10n::t("Community pages for visitors"), Config::get('system','community_page_style'), L10n::t("Which community pages should be available for visitors. Local users always see both pages."), $community_page_style_choices],
'$max_author_posts_community_page' => ['max_author_posts_community_page', L10n::t("Posts per user on community page"), Config::get('system','max_author_posts_community_page'), L10n::t("The maximum number of posts per user on the community page. \x28Not valid for 'Global Community'\x29")], '$max_author_posts_community_page' => ['max_author_posts_community_page', L10n::t("Posts per user on community page"), Config::get('system','max_author_posts_community_page'), L10n::t("The maximum number of posts per user on the community page. \x28Not valid for 'Global Community'\x29")],
'$ostatus_disabled' => ['ostatus_disabled', L10n::t("Enable OStatus support"), !Config::get('system','ostatus_disabled'), L10n::t("Provide built-in OStatus \x28StatusNet, GNU Social etc.\x29 compatibility. All communications in OStatus are public, so privacy warnings will be occasionally displayed.")], '$ostatus_disabled' => ['ostatus_disabled', L10n::t("Enable OStatus support"), !Config::get('system','ostatus_disabled'), L10n::t("Provide built-in OStatus \x28StatusNet, GNU Social etc.\x29 compatibility. All communications in OStatus are public, so privacy warnings will be occasionally displayed.")],
'$ostatus_full_threads' => ['ostatus_full_threads', L10n::t("Only import OStatus threads from our contacts"), Config::get('system','ostatus_full_threads'), L10n::t("Normally we import every content from our OStatus contacts. With this option we only store threads that are started by a contact that is known on our system.")], '$ostatus_full_threads' => ['ostatus_full_threads', L10n::t("Only import OStatus/ActivityPub threads from our contacts"), Config::get('system','ostatus_full_threads'), L10n::t("Normally we import every content from our OStatus and ActivityPub contacts. With this option we only store threads that are started by a contact that is known on our system.")],
'$ostatus_not_able' => L10n::t("OStatus support can only be enabled if threading is enabled."), '$ostatus_not_able' => L10n::t("OStatus support can only be enabled if threading is enabled."),
'$diaspora_able' => $diaspora_able, '$diaspora_able' => $diaspora_able,
'$diaspora_not_able' => L10n::t("Diaspora support can't be enabled because Friendica was installed into a sub directory."), '$diaspora_not_able' => L10n::t("Diaspora support can't be enabled because Friendica was installed into a sub directory."),

View file

@ -533,7 +533,7 @@ function contacts_content(App $a, $update = 0)
$relation_text = ''; $relation_text = '';
} }
if (!in_array($contact['network'], [Protocol::DFRN, Protocol::OSTATUS, Protocol::DIASPORA])) { if (!in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::DIASPORA])) {
$relation_text = ""; $relation_text = "";
} }
@ -555,7 +555,7 @@ function contacts_content(App $a, $update = 0)
} }
$lblsuggest = (($contact['network'] === Protocol::DFRN) ? L10n::t('Suggest friends') : ''); $lblsuggest = (($contact['network'] === Protocol::DFRN) ? L10n::t('Suggest friends') : '');
$poll_enabled = in_array($contact['network'], [Protocol::DFRN, Protocol::OSTATUS, Protocol::FEED, Protocol::MAIL]); $poll_enabled = in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::FEED, Protocol::MAIL]);
$nettype = L10n::t('Network type: %s', ContactSelector::networkToName($contact['network'], $contact["url"])); $nettype = L10n::t('Network type: %s', ContactSelector::networkToName($contact['network'], $contact["url"]));
@ -966,7 +966,7 @@ function contact_conversations(App $a, $contact_id, $update)
$profiledata = Contact::getDetailsByURL($contact["url"]); $profiledata = Contact::getDetailsByURL($contact["url"]);
if (local_user()) { if (local_user()) {
if (in_array($profiledata["network"], [Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) { if (in_array($profiledata["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) {
$profiledata["remoteconnect"] = System::baseUrl()."/follow?url=".urlencode($profiledata["url"]); $profiledata["remoteconnect"] = System::baseUrl()."/follow?url=".urlencode($profiledata["url"]);
} }
} }
@ -990,7 +990,7 @@ function contact_posts(App $a, $contact_id)
$profiledata = Contact::getDetailsByURL($contact["url"]); $profiledata = Contact::getDetailsByURL($contact["url"]);
if (local_user()) { if (local_user()) {
if (in_array($profiledata["network"], [Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) { if (in_array($profiledata["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) {
$profiledata["remoteconnect"] = System::baseUrl()."/follow?url=".urlencode($profiledata["url"]); $profiledata["remoteconnect"] = System::baseUrl()."/follow?url=".urlencode($profiledata["url"]);
} }
} }
@ -1071,7 +1071,7 @@ function _contact_detail_for_template(array $rr)
*/ */
function contact_actions($contact) function contact_actions($contact)
{ {
$poll_enabled = in_array($contact['network'], [Protocol::DFRN, Protocol::OSTATUS, Protocol::FEED, Protocol::MAIL]); $poll_enabled = in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::FEED, Protocol::MAIL]);
$contact_actions = []; $contact_actions = [];
// Provide friend suggestion only for Friendica contacts // Provide friend suggestion only for Friendica contacts

View file

@ -28,6 +28,7 @@ use Friendica\Model\Group;
use Friendica\Model\User; use Friendica\Model\User;
use Friendica\Network\Probe; use Friendica\Network\Probe;
use Friendica\Protocol\Diaspora; use Friendica\Protocol\Diaspora;
use Friendica\Protocol\ActivityPub;
use Friendica\Util\Crypto; use Friendica\Util\Crypto;
use Friendica\Util\DateTimeFormat; use Friendica\Util\DateTimeFormat;
use Friendica\Util\Network; use Friendica\Util\Network;
@ -335,10 +336,17 @@ function dfrn_confirm_post(App $a, $handsfree = null)
intval($contact_id) intval($contact_id)
); );
} else { } else {
if ($network == Protocol::ACTIVITYPUB) {
ActivityPub::transmitContactAccept($contact['url'], $contact['hub-verify'], $uid);
$pending = true;
} else {
$pending = false;
}
// $network !== Protocol::DFRN // $network !== Protocol::DFRN
$network = defaults($contact, 'network', Protocol::OSTATUS); $network = defaults($contact, 'network', Protocol::OSTATUS);
$arr = Probe::uri($contact['url']); $arr = Probe::uri($contact['url'], $network);
$notify = defaults($contact, 'notify' , $arr['notify']); $notify = defaults($contact, 'notify' , $arr['notify']);
$poll = defaults($contact, 'poll' , $arr['poll']); $poll = defaults($contact, 'poll' , $arr['poll']);
@ -348,7 +356,7 @@ function dfrn_confirm_post(App $a, $handsfree = null)
$new_relation = $contact['rel']; $new_relation = $contact['rel'];
$writable = $contact['writable']; $writable = $contact['writable'];
if ($network === Protocol::DIASPORA) { if (in_array($network, [Protocol::DIASPORA, Protocol::ACTIVITYPUB])) {
if ($duplex) { if ($duplex) {
$new_relation = Contact::FRIEND; $new_relation = Contact::FRIEND;
} else { } else {
@ -362,30 +370,12 @@ function dfrn_confirm_post(App $a, $handsfree = null)
DBA::delete('intro', ['id' => $intro_id]); DBA::delete('intro', ['id' => $intro_id]);
$r = q("UPDATE `contact` SET `name-date` = '%s', $fields = ['name-date' => DateTimeFormat::utcNow(),
`uri-date` = '%s', 'uri-date' => DateTimeFormat::utcNow(), 'addr' => $addr,
`addr` = '%s', 'notify' => $notify, 'poll' => $poll, 'blocked' => false,
`notify` = '%s', 'pending' => $pending, 'network' => $network,
`poll` = '%s', 'writable' => $writable, 'hidden' => $hidden, 'rel' => $new_relation];
`blocked` = 0, DBA::update('contact', $fields, ['id' => $contact_id]);
`pending` = 0,
`network` = '%s',
`writable` = %d,
`hidden` = %d,
`rel` = %d
WHERE `id` = %d
",
DBA::escape(DateTimeFormat::utcNow()),
DBA::escape(DateTimeFormat::utcNow()),
DBA::escape($addr),
DBA::escape($notify),
DBA::escape($poll),
DBA::escape($network),
intval($writable),
intval($hidden),
intval($new_relation),
intval($contact_id)
);
} }
if (!DBA::isResult($r)) { if (!DBA::isResult($r)) {
@ -403,6 +393,10 @@ function dfrn_confirm_post(App $a, $handsfree = null)
Group::addMember(User::getDefaultGroup($uid, $contact["network"]), $contact['id']); Group::addMember(User::getDefaultGroup($uid, $contact["network"]), $contact['id']);
if ($network == Protocol::ACTIVITYPUB && $duplex) {
ActivityPub::transmitActivity('Follow', $contact['url'], $uid);
}
// Let's send our user to the contact editor in case they want to // Let's send our user to the contact editor in case they want to
// do anything special with this new friend. // do anything special with this new friend.
if ($handsfree === null) { if ($handsfree === null) {

View file

@ -54,7 +54,7 @@ function dirfind_content(App $a, $prefix = "") {
if ((valid_email($search) && Network::isEmailDomainValid($search)) || if ((valid_email($search) && Network::isEmailDomainValid($search)) ||
(substr(normalise_link($search), 0, 7) == "http://")) { (substr(normalise_link($search), 0, 7) == "http://")) {
$user_data = Probe::uri($search); $user_data = Probe::uri($search);
$discover_user = (in_array($user_data["network"], [Protocol::DFRN, Protocol::OSTATUS, Protocol::DIASPORA])); $discover_user = (in_array($user_data["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::DIASPORA]));
} }
} }

View file

@ -17,6 +17,7 @@ use Friendica\Model\Group;
use Friendica\Model\Item; use Friendica\Model\Item;
use Friendica\Model\Profile; use Friendica\Model\Profile;
use Friendica\Protocol\DFRN; use Friendica\Protocol\DFRN;
use Friendica\Protocol\ActivityPub;
function display_init(App $a) function display_init(App $a)
{ {
@ -43,7 +44,7 @@ function display_init(App $a)
$item = null; $item = null;
$fields = ['id', 'parent', 'author-id', 'body', 'uid']; $fields = ['id', 'parent', 'author-id', 'body', 'uid', 'guid'];
// If there is only one parameter, then check if this parameter could be a guid // If there is only one parameter, then check if this parameter could be a guid
if ($a->argc == 2) { if ($a->argc == 2) {
@ -76,6 +77,10 @@ function display_init(App $a)
displayShowFeed($item["id"], false); displayShowFeed($item["id"], false);
} }
if (ActivityPub::isRequest()) {
goaway(str_replace('display/', 'object/', $a->query_string));
}
if ($item["id"] != $item["parent"]) { if ($item["id"] != $item["parent"]) {
$item = Item::selectFirstForUser(local_user(), $fields, ['id' => $item["parent"]]); $item = Item::selectFirstForUser(local_user(), $fields, ['id' => $item["parent"]]);
} }

View file

@ -159,7 +159,7 @@ function item_post(App $a) {
} }
// Allow commenting if it is an answer to a public post // Allow commenting if it is an answer to a public post
$allow_comment = local_user() && ($profile_uid == 0) && $parent && in_array($parent_item['network'], [Protocol::OSTATUS, Protocol::DIASPORA, Protocol::DFRN]); $allow_comment = local_user() && ($profile_uid == 0) && $parent && in_array($parent_item['network'], [Protocol::ACTIVITYPUB, Protocol::OSTATUS, Protocol::DIASPORA, Protocol::DFRN]);
// Now check that valid personal details have been provided // Now check that valid personal details have been provided
if (!can_write_wall($profile_uid) && !$allow_comment) { if (!can_write_wall($profile_uid) && !$allow_comment) {
@ -240,7 +240,7 @@ function item_post(App $a) {
$emailcc = notags(trim(defaults($_REQUEST, 'emailcc' , ''))); $emailcc = notags(trim(defaults($_REQUEST, 'emailcc' , '')));
$body = escape_tags(trim(defaults($_REQUEST, 'body' , ''))); $body = escape_tags(trim(defaults($_REQUEST, 'body' , '')));
$network = notags(trim(defaults($_REQUEST, 'network' , Protocol::DFRN))); $network = notags(trim(defaults($_REQUEST, 'network' , Protocol::DFRN)));
$guid = System::createGUID(32); $guid = System::createUUID();
$postopts = defaults($_REQUEST, 'postopts', ''); $postopts = defaults($_REQUEST, 'postopts', '');
@ -343,23 +343,14 @@ function item_post(App $a) {
$tags = get_tags($body); $tags = get_tags($body);
// Add a tag if the parent contact is from OStatus (This will notify them during delivery) // Add a tag if the parent contact is from ActivityPub or OStatus (This will notify them)
if ($parent) { if ($parent && in_array($thr_parent_contact['network'], [Protocol::OSTATUS, Protocol::ACTIVITYPUB])) {
if ($thr_parent_contact['network'] == Protocol::OSTATUS) {
$contact = '@[url=' . $thr_parent_contact['url'] . ']' . $thr_parent_contact['nick'] . '[/url]'; $contact = '@[url=' . $thr_parent_contact['url'] . ']' . $thr_parent_contact['nick'] . '[/url]';
if (!stripos(implode($tags), '[url=' . $thr_parent_contact['url'] . ']')) { if (!stripos(implode($tags), '[url=' . $thr_parent_contact['url'] . ']')) {
$tags[] = $contact; $tags[] = $contact;
} }
} }
if ($parent_contact['network'] == Protocol::OSTATUS) {
$contact = '@[url=' . $parent_contact['url'] . ']' . $parent_contact['nick'] . '[/url]';
if (!stripos(implode($tags), '[url=' . $parent_contact['url'] . ']')) {
$tags[] = $contact;
}
}
}
$tagged = []; $tagged = [];
$private_forum = false; $private_forum = false;
@ -1026,8 +1017,7 @@ function handle_tag(App $a, &$body, &$inform, &$str_tags, $profile_uid, $tag, $n
$alias = $contact["alias"]; $alias = $contact["alias"];
$newname = $contact["nick"]; $newname = $contact["nick"];
if (($newname == "") || (($contact["network"] != Protocol::OSTATUS) && ($contact["network"] != Protocol::TWITTER) if (($newname == "") || !in_array($contact["network"], [Protocol::ACTIVITYPUB, Protocol::OSTATUS, Protocol::TWITTER, Protocol::STATUSNET])) {
&& ($contact["network"] != Protocol::STATUSNET))) {
$newname = $contact["name"]; $newname = $contact["name"];
} }
} }

View file

@ -472,7 +472,7 @@ function photos_post(App $a)
$uri = Item::newURI($page_owner_uid); $uri = Item::newURI($page_owner_uid);
$arr = []; $arr = [];
$arr['guid'] = System::createGUID(32); $arr['guid'] = System::createUUID();
$arr['uid'] = $page_owner_uid; $arr['uid'] = $page_owner_uid;
$arr['uri'] = $uri; $arr['uri'] = $uri;
$arr['parent-uri'] = $uri; $arr['parent-uri'] = $uri;
@ -651,7 +651,7 @@ function photos_post(App $a)
$uri = Item::newURI($page_owner_uid); $uri = Item::newURI($page_owner_uid);
$arr = []; $arr = [];
$arr['guid'] = System::createGUID(32); $arr['guid'] = System::createUUID();
$arr['uid'] = $page_owner_uid; $arr['uid'] = $page_owner_uid;
$arr['uri'] = $uri; $arr['uri'] = $uri;
$arr['parent-uri'] = $uri; $arr['parent-uri'] = $uri;
@ -889,7 +889,7 @@ function photos_post(App $a)
$arr['coord'] = $lat . ' ' . $lon; $arr['coord'] = $lat . ' ' . $lon;
} }
$arr['guid'] = System::createGUID(32); $arr['guid'] = System::createUUID();
$arr['uid'] = $page_owner_uid; $arr['uid'] = $page_owner_uid;
$arr['uri'] = $uri; $arr['uri'] = $uri;
$arr['parent-uri'] = $uri; $arr['parent-uri'] = $uri;

View file

@ -97,7 +97,7 @@ function poke_init(App $a)
$arr = []; $arr = [];
$arr['guid'] = System::createGUID(32); $arr['guid'] = System::createUUID();
$arr['uid'] = $uid; $arr['uid'] = $uid;
$arr['uri'] = $uri; $arr['uri'] = $uri;
$arr['parent-uri'] = (!empty($parent_uri) ? $parent_uri : $uri); $arr['parent-uri'] = (!empty($parent_uri) ? $parent_uri : $uri);

View file

@ -20,6 +20,7 @@ use Friendica\Model\Profile;
use Friendica\Module\Login; use Friendica\Module\Login;
use Friendica\Protocol\DFRN; use Friendica\Protocol\DFRN;
use Friendica\Util\DateTimeFormat; use Friendica\Util\DateTimeFormat;
use Friendica\Protocol\ActivityPub;
function profile_init(App $a) function profile_init(App $a)
{ {
@ -49,6 +50,16 @@ function profile_init(App $a)
DFRN::autoRedir($a, $which); DFRN::autoRedir($a, $which);
} }
if (ActivityPub::isRequest()) {
$user = DBA::selectFirst('user', ['uid'], ['nickname' => $which]);
if (DBA::isResult($user)) {
$data = ActivityPub::profile($user['uid']);
echo json_encode($data);
header('Content-Type: application/activity+json');
exit();
}
}
Profile::load($a, $which, $profile); Profile::load($a, $which, $profile);
$blocked = !local_user() && !remote_user() && Config::get('system', 'block_public'); $blocked = !local_user() && !remote_user() && Config::get('system', 'block_public');

View file

@ -108,7 +108,7 @@ EOT;
$arr = []; $arr = [];
$arr['guid'] = System::createGUID(32); $arr['guid'] = System::createUUID();
$arr['uri'] = $uri; $arr['uri'] = $uri;
$arr['uid'] = $owner_uid; $arr['uid'] = $owner_uid;
$arr['contact-id'] = $contact['id']; $arr['contact-id'] = $contact['id'];

View file

@ -115,7 +115,7 @@ EOT;
$arr = []; $arr = [];
$arr['guid'] = System::createGUID(32); $arr['guid'] = System::createUUID();
$arr['uri'] = $uri; $arr['uri'] = $uri;
$arr['uid'] = $owner_uid; $arr['uid'] = $owner_uid;
$arr['contact-id'] = $contact['id']; $arr['contact-id'] = $contact['id'];

View file

@ -80,6 +80,7 @@ function xrd_json($a, $uri, $alias, $profile_url, $r)
['rel' => NAMESPACE_DFRN, 'href' => $profile_url], ['rel' => NAMESPACE_DFRN, 'href' => $profile_url],
['rel' => NAMESPACE_FEED, 'type' => 'application/atom+xml', 'href' => System::baseUrl().'/dfrn_poll/'.$r['nickname']], ['rel' => NAMESPACE_FEED, 'type' => 'application/atom+xml', 'href' => System::baseUrl().'/dfrn_poll/'.$r['nickname']],
['rel' => 'http://webfinger.net/rel/profile-page', 'type' => 'text/html', 'href' => $profile_url], ['rel' => 'http://webfinger.net/rel/profile-page', 'type' => 'text/html', 'href' => $profile_url],
['rel' => 'self', 'type' => 'application/activity+json', 'href' => $profile_url],
['rel' => 'http://microformats.org/profile/hcard', 'type' => 'text/html', 'href' => System::baseUrl().'/hcard/'.$r['nickname']], ['rel' => 'http://microformats.org/profile/hcard', 'type' => 'text/html', 'href' => System::baseUrl().'/hcard/'.$r['nickname']],
['rel' => NAMESPACE_POCO, 'href' => System::baseUrl().'/poco/'.$r['nickname']], ['rel' => NAMESPACE_POCO, 'href' => System::baseUrl().'/poco/'.$r['nickname']],
['rel' => 'http://webfinger.net/rel/avatar', 'type' => 'image/jpeg', 'href' => System::baseUrl().'/photo/profile/'.$r['uid'].'.jpg'], ['rel' => 'http://webfinger.net/rel/avatar', 'type' => 'image/jpeg', 'href' => System::baseUrl().'/photo/profile/'.$r['uid'].'.jpg'],
@ -92,6 +93,7 @@ function xrd_json($a, $uri, $alias, $profile_url, $r)
['rel' => 'http://purl.org/openwebauth/v1', 'type' => 'application/x-dfrn+json', 'href' => System::baseUrl().'/owa'] ['rel' => 'http://purl.org/openwebauth/v1', 'type' => 'application/x-dfrn+json', 'href' => System::baseUrl().'/owa']
] ]
]; ];
echo json_encode($json); echo json_encode($json);
killme(); killme();
} }

View file

@ -21,7 +21,16 @@ abstract class BaseModule extends BaseObject
*/ */
public static function init() public static function init()
{ {
}
/**
* @brief Module GET method to display raw content from technical endpoints
*
* Extend this method if the module is supposed to return communication data,
* e.g. from protocol implementations.
*/
public static function rawContent()
{
} }
/** /**

View file

@ -89,6 +89,7 @@ class ContactSelector
Protocol::TWITTER => L10n::t('Twitter'), Protocol::TWITTER => L10n::t('Twitter'),
Protocol::DIASPORA2 => L10n::t('Diaspora Connector'), Protocol::DIASPORA2 => L10n::t('Diaspora Connector'),
Protocol::STATUSNET => L10n::t('GNU Social Connector'), Protocol::STATUSNET => L10n::t('GNU Social Connector'),
Protocol::ACTIVITYPUB => L10n::t('ActivityPub'),
Protocol::PNUT => L10n::t('pnut'), Protocol::PNUT => L10n::t('pnut'),
]; ];
@ -99,13 +100,17 @@ class ContactSelector
$networkname = str_replace($search, $replace, $s); $networkname = str_replace($search, $replace, $s);
if ((in_array($s, [Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) && ($profile != "")) { if ((in_array($s, [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) && ($profile != "")) {
$r = DBA::fetchFirst("SELECT `gserver`.`platform` FROM `gcontact` $r = DBA::fetchFirst("SELECT `gserver`.`platform` FROM `gcontact`
INNER JOIN `gserver` ON `gserver`.`nurl` = `gcontact`.`server_url` INNER JOIN `gserver` ON `gserver`.`nurl` = `gcontact`.`server_url`
WHERE `gcontact`.`nurl` = ? AND `platform` != ''", normalise_link($profile)); WHERE `gcontact`.`nurl` = ? AND `platform` != ''", normalise_link($profile));
if (DBA::isResult($r)) { if (DBA::isResult($r)) {
$networkname = $r['platform']; $networkname = $r['platform'];
if ($s == Protocol::ACTIVITYPUB) {
$networkname .= ' (AP)';
}
} }
} }

View file

@ -142,11 +142,8 @@ class Widget
$nets = array(); $nets = array();
while ($rr = DBA::fetch($r)) { while ($rr = DBA::fetch($r)) {
/// @TODO If 'network' is not there, this triggers an E_NOTICE
if ($rr['network']) {
$nets[] = array('ref' => $rr['network'], 'name' => ContactSelector::networkToName($rr['network']), 'selected' => (($selected == $rr['network']) ? 'selected' : '' )); $nets[] = array('ref' => $rr['network'], 'name' => ContactSelector::networkToName($rr['network']), 'selected' => (($selected == $rr['network']) ? 'selected' : '' ));
} }
}
DBA::close($r); DBA::close($r);
if (count($nets) < 2) { if (count($nets) < 2) {

View file

@ -161,6 +161,18 @@ class System extends BaseObject
killme(); killme();
} }
/**
* Generates a random string in the UUID format
*
* @param bool|string $prefix A given prefix (default is empty)
* @return string a generated UUID
*/
public static function createUUID($prefix = '')
{
$guid = System::createGUID(32, $prefix);
return substr($guid, 0, 8). '-' . substr($guid, 8, 4) . '-' . substr($guid, 12, 4) . '-' . substr($guid, 16, 4) . '-' . substr($guid, 20, 12);
}
/** /**
* Generates a GUID with the given parameters * Generates a GUID with the given parameters
* *

175
src/Model/APContact.php Normal file
View file

@ -0,0 +1,175 @@
<?php
/**
* @file src/Model/APContact.php
*/
namespace Friendica\Model;
use Friendica\BaseObject;
use Friendica\Database\DBA;
use Friendica\Protocol\ActivityPub;
use Friendica\Util\Network;
use Friendica\Util\JsonLD;
use Friendica\Util\DateTimeFormat;
require_once 'boot.php';
class APContact extends BaseObject
{
/**
* Resolves the profile url from the address by using webfinger
*
* @param string $addr profile address (user@domain.tld)
* @return string url
*/
private static function addrToUrl($addr)
{
$addr_parts = explode('@', $addr);
if (count($addr_parts) != 2) {
return false;
}
$webfinger = 'https://' . $addr_parts[1] . '/.well-known/webfinger?resource=acct:' . urlencode($addr);
$ret = Network::curl($webfinger, false, $redirects, ['accept_content' => 'application/jrd+json,application/json']);
if (!$ret['success'] || empty($ret['body'])) {
return false;
}
$data = json_decode($ret['body'], true);
if (empty($data['links'])) {
return false;
}
foreach ($data['links'] as $link) {
if (empty($link['href']) || empty($link['rel']) || empty($link['type'])) {
continue;
}
if (($link['rel'] == 'self') && ($link['type'] == 'application/activity+json')) {
return $link['href'];
}
}
return false;
}
/**
* Fetches a profile from a given url
*
* @param string $url profile url
* @param boolean $update true = always update, false = never update, null = update when not found
* @return array profile array
*/
public static function getByURL($url, $update = null)
{
if (empty($url)) {
return false;
}
if (empty($update)) {
$apcontact = DBA::selectFirst('apcontact', [], ['url' => $url]);
if (DBA::isResult($apcontact)) {
return $apcontact;
}
$apcontact = DBA::selectFirst('apcontact', [], ['alias' => $url]);
if (DBA::isResult($apcontact)) {
return $apcontact;
}
$apcontact = DBA::selectFirst('apcontact', [], ['addr' => $url]);
if (DBA::isResult($apcontact)) {
return $apcontact;
}
if (!is_null($update)) {
return false;
}
}
if (empty(parse_url($url, PHP_URL_SCHEME))) {
$url = self::addrToUrl($url);
if (empty($url)) {
return false;
}
}
$data = ActivityPub::fetchContent($url);
if (empty($data) || empty($data['id']) || empty($data['inbox'])) {
return false;
}
$apcontact = [];
$apcontact['url'] = $data['id'];
$apcontact['uuid'] = defaults($data, 'diaspora:guid', null);
$apcontact['type'] = defaults($data, 'type', null);
$apcontact['following'] = defaults($data, 'following', null);
$apcontact['followers'] = defaults($data, 'followers', null);
$apcontact['inbox'] = defaults($data, 'inbox', null);
$apcontact['outbox'] = defaults($data, 'outbox', null);
$apcontact['sharedinbox'] = JsonLD::fetchElement($data, 'endpoints', 'sharedInbox');
$apcontact['nick'] = defaults($data, 'preferredUsername', null);
$apcontact['name'] = defaults($data, 'name', $apcontact['nick']);
$apcontact['about'] = defaults($data, 'summary', '');
$apcontact['photo'] = JsonLD::fetchElement($data, 'icon', 'url');
$apcontact['alias'] = JsonLD::fetchElement($data, 'url', 'href');
$parts = parse_url($apcontact['url']);
unset($parts['scheme']);
unset($parts['path']);
$apcontact['addr'] = $apcontact['nick'] . '@' . str_replace('//', '', Network::unparseURL($parts));
$apcontact['pubkey'] = trim(JsonLD::fetchElement($data, 'publicKey', 'publicKeyPem'));
// To-Do
// manuallyApprovesFollowers
// Unhandled
// @context, tag, attachment, image, nomadicLocations, signature, following, followers, featured, movedTo, liked
// Unhandled from Misskey
// sharedInbox, isCat
// Unhandled from Kroeg
// kroeg:blocks, updated
// Check if the address is resolvable
if (self::addrToUrl($apcontact['addr']) == $apcontact['url']) {
$parts = parse_url($apcontact['url']);
unset($parts['path']);
$apcontact['baseurl'] = Network::unparseURL($parts);
} else {
$apcontact['addr'] = null;
$apcontact['baseurl'] = null;
}
if ($apcontact['url'] == $apcontact['alias']) {
$apcontact['alias'] = null;
}
$apcontact['updated'] = DateTimeFormat::utcNow();
DBA::update('apcontact', $apcontact, ['url' => $url], true);
// Update some data in the contact table with various ways to catch them all
$contact_fields = ['name' => $apcontact['name'], 'about' => $apcontact['about']];
DBA::update('contact', $contact_fields, ['nurl' => normalise_link($url)]);
$contacts = DBA::select('contact', ['uid', 'id'], ['nurl' => normalise_link($url)]);
while ($contact = DBA::fetch($contacts)) {
Contact::updateAvatar($apcontact['photo'], $contact['uid'], $contact['id']);
}
DBA::close($contacts);
// Update the gcontact table
DBA::update('gcontact', $contact_fields, ['nurl' => normalise_link($url)]);
logger('Updated profile for ' . $url, LOGGER_DEBUG);
return $apcontact;
}
}

View file

@ -16,6 +16,7 @@ use Friendica\Database\DBA;
use Friendica\Model\Profile; use Friendica\Model\Profile;
use Friendica\Network\Probe; use Friendica\Network\Probe;
use Friendica\Object\Image; use Friendica\Object\Image;
use Friendica\Protocol\ActivityPub;
use Friendica\Protocol\Diaspora; use Friendica\Protocol\Diaspora;
use Friendica\Protocol\DFRN; use Friendica\Protocol\DFRN;
use Friendica\Protocol\OStatus; use Friendica\Protocol\OStatus;
@ -555,6 +556,12 @@ class Contact extends BaseObject
} }
} elseif ($contact['network'] == Protocol::DIASPORA) { } elseif ($contact['network'] == Protocol::DIASPORA) {
Diaspora::sendUnshare($user, $contact); Diaspora::sendUnshare($user, $contact);
} elseif ($contact['network'] == Protocol::ACTIVITYPUB) {
ActivityPub::transmitContactUndo($contact['url'], $user['uid']);
if ($dissolve) {
ActivityPub::transmitContactReject($contact['url'], $contact['hub-verify'], $user['uid']);
}
} }
} }
@ -775,7 +782,7 @@ class Contact extends BaseObject
} }
if ((empty($profile["addr"]) || empty($profile["name"])) && (defaults($profile, "gid", 0) != 0) if ((empty($profile["addr"]) || empty($profile["name"])) && (defaults($profile, "gid", 0) != 0)
&& in_array($profile["network"], [Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS]) && in_array($profile["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])
) { ) {
Worker::add(PRIORITY_LOW, "UpdateGContact", $profile["gid"]); Worker::add(PRIORITY_LOW, "UpdateGContact", $profile["gid"]);
} }
@ -1054,7 +1061,6 @@ class Contact extends BaseObject
if (!x($contact, 'avatar')) { if (!x($contact, 'avatar')) {
$update_contact = true; $update_contact = true;
} }
if (!$update_contact || $no_update) { if (!$update_contact || $no_update) {
return $contact_id; return $contact_id;
} }
@ -1088,7 +1094,7 @@ class Contact extends BaseObject
} }
// Last try in gcontact for unsupported networks // Last try in gcontact for unsupported networks
if (!in_array($data["network"], [Protocol::DFRN, Protocol::OSTATUS, Protocol::DIASPORA, Protocol::PUMPIO, Protocol::MAIL, Protocol::FEED])) { if (!in_array($data["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::DIASPORA, Protocol::PUMPIO, Protocol::MAIL, Protocol::FEED])) {
if ($uid != 0) { if ($uid != 0) {
return 0; return 0;
} }
@ -1316,33 +1322,27 @@ class Contact extends BaseObject
require_once 'include/conversation.php'; require_once 'include/conversation.php';
// There are no posts with "uid = 0" with connector networks $cid = Self::getIdForURL($contact_url);
// This speeds up the query a lot
$r = q("SELECT `network`, `id` AS `author-id`, `contact-type` FROM `contact`
WHERE `contact`.`nurl` = '%s' AND `contact`.`uid` = 0",
DBA::escape(normalise_link($contact_url))
);
if (!DBA::isResult($r)) { $contact = DBA::selectFirst('contact', ['contact-type', 'network'], ['id' => $cid]);
if (!DBA::isResult($contact)) {
return ''; return '';
} }
if (in_array($r[0]["network"], [Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, ""])) { if (in_array($contact["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, ""])) {
$sql = "(`item`.`uid` = 0 OR (`item`.`uid` = ? AND NOT `item`.`global`))"; $sql = "(`item`.`uid` = 0 OR (`item`.`uid` = ? AND NOT `item`.`global`))";
} else { } else {
$sql = "`item`.`uid` = ?"; $sql = "`item`.`uid` = ?";
} }
$author_id = intval($r[0]["author-id"]); $contact_field = ($contact["contact-type"] == self::ACCOUNT_TYPE_COMMUNITY ? 'owner-id' : 'author-id');
$contact = ($r[0]["contact-type"] == self::ACCOUNT_TYPE_COMMUNITY ? 'owner-id' : 'author-id');
if ($thread_mode) { if ($thread_mode) {
$condition = ["`$contact` = ? AND `gravity` = ? AND " . $sql, $condition = ["`$contact_field` = ? AND `gravity` = ? AND " . $sql,
$author_id, GRAVITY_PARENT, local_user()]; $cid, GRAVITY_PARENT, local_user()];
} else { } else {
$condition = ["`$contact` = ? AND `gravity` IN (?, ?) AND " . $sql, $condition = ["`$contact_field` = ? AND `gravity` IN (?, ?) AND " . $sql,
$author_id, GRAVITY_PARENT, GRAVITY_COMMENT, local_user()]; $cid, GRAVITY_PARENT, GRAVITY_COMMENT, local_user()];
} }
$params = ['order' => ['created' => true], $params = ['order' => ['created' => true],
@ -1496,9 +1496,10 @@ class Contact extends BaseObject
/** /**
* @param integer $id contact id * @param integer $id contact id
* @param string $network Optional network we are probing for
* @return boolean * @return boolean
*/ */
public static function updateFromProbe($id) public static function updateFromProbe($id, $network = '')
{ {
/* /*
Warning: Never ever fetch the public key via Probe::uri and write it into the contacts. Warning: Never ever fetch the public key via Probe::uri and write it into the contacts.
@ -1511,10 +1512,10 @@ class Contact extends BaseObject
return false; return false;
} }
$ret = Probe::uri($contact["url"]); $ret = Probe::uri($contact["url"], $network);
// If Probe::uri fails the network code will be different // If Probe::uri fails the network code will be different
if ($ret["network"] != $contact["network"]) { if (($ret["network"] != $contact["network"]) && ($ret["network"] != $network)) {
return false; return false;
} }
@ -1539,6 +1540,7 @@ class Contact extends BaseObject
'contact', [ 'contact', [
'url' => $ret['url'], 'url' => $ret['url'],
'nurl' => normalise_link($ret['url']), 'nurl' => normalise_link($ret['url']),
'network' => $ret['network'],
'addr' => $ret['addr'], 'addr' => $ret['addr'],
'alias' => $ret['alias'], 'alias' => $ret['alias'],
'batch' => $ret['batch'], 'batch' => $ret['batch'],
@ -1686,7 +1688,7 @@ class Contact extends BaseObject
$hidden = (($ret['network'] === Protocol::MAIL) ? 1 : 0); $hidden = (($ret['network'] === Protocol::MAIL) ? 1 : 0);
if (in_array($ret['network'], [Protocol::MAIL, Protocol::DIASPORA])) { if (in_array($ret['network'], [Protocol::MAIL, Protocol::DIASPORA, Protocol::ACTIVITYPUB])) {
$writeable = 1; $writeable = 1;
} }
@ -1766,6 +1768,9 @@ class Contact extends BaseObject
} elseif ($contact['network'] == Protocol::DIASPORA) { } elseif ($contact['network'] == Protocol::DIASPORA) {
$ret = Diaspora::sendShare($a->user, $contact); $ret = Diaspora::sendShare($a->user, $contact);
logger('share returns: ' . $ret); logger('share returns: ' . $ret);
} elseif ($contact['network'] == Protocol::ACTIVITYPUB) {
$ret = ActivityPub::transmitActivity('Follow', $contact['url'], $uid);
logger('Follow returns: ' . $ret);
} }
} }
@ -1814,7 +1819,7 @@ class Contact extends BaseObject
return $contact; return $contact;
} }
public static function addRelationship($importer, $contact, $datarray, $item, $sharing = false) { public static function addRelationship($importer, $contact, $datarray, $item = '', $sharing = false) {
// Should always be set // Should always be set
if (empty($datarray['author-id'])) { if (empty($datarray['author-id'])) {
return; return;
@ -1827,7 +1832,7 @@ class Contact extends BaseObject
return; return;
} }
$url = $pub_contact['url']; $url = defaults($datarray, 'author-link', $pub_contact['url']);
$name = $pub_contact['name']; $name = $pub_contact['name'];
$photo = $pub_contact['photo']; $photo = $pub_contact['photo'];
$nick = $pub_contact['nick']; $nick = $pub_contact['nick'];
@ -1839,13 +1844,17 @@ class Contact extends BaseObject
DBA::update('contact', ['rel' => self::FRIEND, 'writable' => true], DBA::update('contact', ['rel' => self::FRIEND, 'writable' => true],
['id' => $contact['id'], 'uid' => $importer['uid']]); ['id' => $contact['id'], 'uid' => $importer['uid']]);
} }
if ($contact['network'] == Protocol::ACTIVITYPUB) {
ActivityPub::transmitContactAccept($contact['url'], $contact['hub-verify'], $importer['uid']);
}
// send email notification to owner? // send email notification to owner?
} else { } else {
if (DBA::exists('contact', ['nurl' => normalise_link($url), 'uid' => $importer['uid'], 'pending' => true])) { if (DBA::exists('contact', ['nurl' => normalise_link($url), 'uid' => $importer['uid'], 'pending' => true])) {
logger('ignoring duplicated connection request from pending contact ' . $url); logger('ignoring duplicated connection request from pending contact ' . $url);
return; return;
} }
// create contact record // create contact record
q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`, q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
`blocked`, `readonly`, `pending`, `writable`) `blocked`, `readonly`, `pending`, `writable`)

View file

@ -17,13 +17,14 @@ class Conversation
* These constants represent the parcel format used to transport a conversation independently of the message protocol. * These constants represent the parcel format used to transport a conversation independently of the message protocol.
* It currently is stored in the "protocol" field for legacy reasons. * It currently is stored in the "protocol" field for legacy reasons.
*/ */
const PARCEL_UNKNOWN = 0; const PARCEL_ACTIVITYPUB = 0;
const PARCEL_DFRN = 1; const PARCEL_DFRN = 1;
const PARCEL_DIASPORA = 2; const PARCEL_DIASPORA = 2;
const PARCEL_SALMON = 3; const PARCEL_SALMON = 3;
const PARCEL_FEED = 4; // Deprecated const PARCEL_FEED = 4; // Deprecated
const PARCEL_SPLIT_CONVERSATION = 6; const PARCEL_SPLIT_CONVERSATION = 6;
const PARCEL_TWITTER = 67; const PARCEL_TWITTER = 67;
const PARCEL_UNKNOWN = 255;
/** /**
* @brief Store the conversation data * @brief Store the conversation data
@ -34,7 +35,7 @@ class Conversation
public static function insert(array $arr) public static function insert(array $arr)
{ {
if (in_array(defaults($arr, 'network', Protocol::PHANTOM), if (in_array(defaults($arr, 'network', Protocol::PHANTOM),
[Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, Protocol::TWITTER]) && !empty($arr['uri'])) { [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, Protocol::TWITTER]) && !empty($arr['uri'])) {
$conversation = ['item-uri' => $arr['uri'], 'received' => DateTimeFormat::utcNow()]; $conversation = ['item-uri' => $arr['uri'], 'received' => DateTimeFormat::utcNow()];
if (isset($arr['parent-uri']) && ($arr['parent-uri'] != $arr['uri'])) { if (isset($arr['parent-uri']) && ($arr['parent-uri'] != $arr['uri'])) {
@ -70,7 +71,8 @@ class Conversation
unset($old_conv['source']); unset($old_conv['source']);
} }
// Update structure data all the time but the source only when its from a better protocol. // Update structure data all the time but the source only when its from a better protocol.
if (isset($conversation['protocol']) && isset($conversation['source']) && ($old_conv['protocol'] < $conversation['protocol']) && ($old_conv['protocol'] != 0)) { if (empty($conversation['source']) || (!empty($old_conv['source']) &&
($old_conv['protocol'] < defaults($conversation, 'protocol', PARCEL_UNKNOWN)))) {
unset($conversation['protocol']); unset($conversation['protocol']);
unset($conversation['source']); unset($conversation['source']);
} }

View file

@ -314,7 +314,7 @@ class Event extends BaseObject
Addon::callHooks('event_updated', $event['id']); Addon::callHooks('event_updated', $event['id']);
} else { } else {
$event['guid'] = defaults($arr, 'guid', System::createGUID(32)); $event['guid'] = defaults($arr, 'guid', System::createUUID());
// New event. Store it. // New event. Store it.
DBA::insert('event', $event); DBA::insert('event', $event);

View file

@ -176,7 +176,7 @@ class Item extends BaseObject
// We can always comment on posts from these networks // We can always comment on posts from these networks
if (array_key_exists('writable', $row) && if (array_key_exists('writable', $row) &&
in_array($row['internal-network'], [Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) { in_array($row['internal-network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) {
$row['writable'] = true; $row['writable'] = true;
} }
@ -1081,9 +1081,8 @@ class Item extends BaseObject
DBA::delete('item-delivery-data', ['iid' => $item['id']]); DBA::delete('item-delivery-data', ['iid' => $item['id']]);
if (!empty($item['iaid']) && !self::exists(['iaid' => $item['iaid'], 'deleted' => false])) { // We don't delete the item-activity here, since we need some of the data for ActivityPub
DBA::delete('item-activity', ['id' => $item['iaid']], ['cascade' => false]);
}
if (!empty($item['icid']) && !self::exists(['icid' => $item['icid'], 'deleted' => false])) { if (!empty($item['icid']) && !self::exists(['icid' => $item['icid'], 'deleted' => false])) {
DBA::delete('item-content', ['id' => $item['icid']], ['cascade' => false]); DBA::delete('item-content', ['id' => $item['icid']], ['cascade' => false]);
} }
@ -1205,7 +1204,7 @@ class Item extends BaseObject
} elseif (!empty($item['uri'])) { } elseif (!empty($item['uri'])) {
$guid = self::guidFromUri($item['uri'], $prefix_host); $guid = self::guidFromUri($item['uri'], $prefix_host);
} else { } else {
$guid = System::createGUID(32, hash('crc32', $prefix_host)); $guid = System::createUUID(hash('crc32', $prefix_host));
} }
return $guid; return $guid;
@ -1352,7 +1351,7 @@ class Item extends BaseObject
* We have to check several networks since Friendica posts could be repeated * We have to check several networks since Friendica posts could be repeated
* via OStatus (maybe Diasporsa as well) * via OStatus (maybe Diasporsa as well)
*/ */
if (in_array($item['network'], [Protocol::DIASPORA, Protocol::DFRN, Protocol::OSTATUS, ""])) { if (in_array($item['network'], [Protocol::ACTIVITYPUB, Protocol::DIASPORA, Protocol::DFRN, Protocol::OSTATUS, ""])) {
$condition = ["`uri` = ? AND `uid` = ? AND `network` IN (?, ?, ?)", $condition = ["`uri` = ? AND `uid` = ? AND `network` IN (?, ?, ?)",
trim($item['uri']), $item['uid'], trim($item['uri']), $item['uid'],
Protocol::DIASPORA, Protocol::DFRN, Protocol::OSTATUS]; Protocol::DIASPORA, Protocol::DFRN, Protocol::OSTATUS];
@ -2054,7 +2053,7 @@ class Item extends BaseObject
// Only distribute public items from native networks // Only distribute public items from native networks
$condition = ['id' => $itemid, 'uid' => 0, $condition = ['id' => $itemid, 'uid' => 0,
'network' => [Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, ""], 'network' => [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, ""],
'visible' => true, 'deleted' => false, 'moderated' => false, 'private' => false]; 'visible' => true, 'deleted' => false, 'moderated' => false, 'private' => false];
$item = self::selectFirst(self::ITEM_FIELDLIST, ['id' => $itemid]); $item = self::selectFirst(self::ITEM_FIELDLIST, ['id' => $itemid]);
if (!DBA::isResult($item)) { if (!DBA::isResult($item)) {
@ -2072,14 +2071,46 @@ class Item extends BaseObject
$users = []; $users = [];
$condition = ["`nurl` IN (SELECT `nurl` FROM `contact` WHERE `id` = ?) AND `uid` != 0 AND NOT `blocked` AND `rel` IN (?, ?)", /// @todo add a field "pcid" in the contact table that referrs to the public contact id.
$parent['owner-id'], Contact::SHARING, Contact::FRIEND]; $owner = DBA::selectFirst('contact', ['url', 'nurl', 'alias'], ['id' => $parent['owner-id']]);
if (!DBA::isResult($owner)) {
return;
}
$condition = ['nurl' => $owner['nurl'], 'rel' => [Contact::SHARING, Contact::FRIEND]];
$contacts = DBA::select('contact', ['uid'], $condition); $contacts = DBA::select('contact', ['uid'], $condition);
while ($contact = DBA::fetch($contacts)) { while ($contact = DBA::fetch($contacts)) {
if ($contact['uid'] == 0) {
continue;
}
$users[$contact['uid']] = $contact['uid']; $users[$contact['uid']] = $contact['uid'];
} }
DBA::close($contacts);
$condition = ['alias' => $owner['url'], 'rel' => [Contact::SHARING, Contact::FRIEND]];
$contacts = DBA::select('contact', ['uid'], $condition);
while ($contact = DBA::fetch($contacts)) {
if ($contact['uid'] == 0) {
continue;
}
$users[$contact['uid']] = $contact['uid'];
}
DBA::close($contacts);
if (!empty($owner['alias'])) {
$condition = ['url' => $owner['alias'], 'rel' => [Contact::SHARING, Contact::FRIEND]];
$contacts = DBA::select('contact', ['uid'], $condition);
while ($contact = DBA::fetch($contacts)) {
if ($contact['uid'] == 0) {
continue;
}
$users[$contact['uid']] = $contact['uid'];
}
DBA::close($contacts);
}
$origin_uid = 0; $origin_uid = 0;
@ -2176,7 +2207,7 @@ class Item extends BaseObject
} }
// is it an entry from a connector? Only add an entry for natively connected networks // is it an entry from a connector? Only add an entry for natively connected networks
if (!in_array($item["network"], [Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, ""])) { if (!in_array($item["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, ""])) {
return; return;
} }
@ -2327,16 +2358,10 @@ class Item extends BaseObject
public static function newURI($uid, $guid = "") public static function newURI($uid, $guid = "")
{ {
if ($guid == "") { if ($guid == "") {
$guid = System::createGUID(32); $guid = System::createUUID();
} }
$hostname = self::getApp()->get_hostname(); return self::getApp()->get_baseurl() . '/object/' . $guid;
$user = DBA::selectFirst('user', ['nickname'], ['uid' => $uid]);
$uri = "urn:X-dfrn:" . $hostname . ':' . $user['nickname'] . ':' . $guid;
return $uri;
} }
/** /**
@ -2660,7 +2685,7 @@ class Item extends BaseObject
} }
if ($contact['network'] != Protocol::FEED) { if ($contact['network'] != Protocol::FEED) {
$datarray["guid"] = System::createGUID(32); $datarray["guid"] = System::createUUID();
unset($datarray["plink"]); unset($datarray["plink"]);
$datarray["uri"] = self::newURI($contact['uid'], $datarray["guid"]); $datarray["uri"] = self::newURI($contact['uid'], $datarray["guid"]);
$datarray["parent-uri"] = $datarray["uri"]; $datarray["parent-uri"] = $datarray["uri"];
@ -2826,7 +2851,7 @@ class Item extends BaseObject
} }
// returns an array of contact-ids that are allowed to see this object // returns an array of contact-ids that are allowed to see this object
private static function enumeratePermissions($obj) public static function enumeratePermissions($obj)
{ {
$allow_people = expand_acl($obj['allow_cid']); $allow_people = expand_acl($obj['allow_cid']);
$allow_groups = Group::expand(expand_acl($obj['allow_gid'])); $allow_groups = Group::expand(expand_acl($obj['allow_gid']));
@ -3089,7 +3114,7 @@ class Item extends BaseObject
$objtype = $item['resource-id'] ? ACTIVITY_OBJ_IMAGE : ACTIVITY_OBJ_NOTE ; $objtype = $item['resource-id'] ? ACTIVITY_OBJ_IMAGE : ACTIVITY_OBJ_NOTE ;
$new_item = [ $new_item = [
'guid' => System::createGUID(32), 'guid' => System::createUUID(),
'uri' => self::newURI($item['uid']), 'uri' => self::newURI($item['uid']),
'uid' => $item['uid'], 'uid' => $item['uid'],
'contact-id' => $item_contact_id, 'contact-id' => $item_contact_id,

View file

@ -46,7 +46,7 @@ class Mail
return -2; return -2;
} }
$guid = System::createGUID(32); $guid = System::createUUID();
$uri = 'urn:X-dfrn:' . System::baseUrl() . ':' . local_user() . ':' . $guid; $uri = 'urn:X-dfrn:' . System::baseUrl() . ':' . local_user() . ':' . $guid;
$convid = 0; $convid = 0;
@ -73,7 +73,7 @@ class Mail
$recip_handle = (($contact['addr']) ? $contact['addr'] : $contact['nick'] . '@' . $recip_host); $recip_handle = (($contact['addr']) ? $contact['addr'] : $contact['nick'] . '@' . $recip_host);
$sender_handle = $a->user['nickname'] . '@' . substr(System::baseUrl(), strpos(System::baseUrl(), '://') + 3); $sender_handle = $a->user['nickname'] . '@' . substr(System::baseUrl(), strpos(System::baseUrl(), '://') + 3);
$conv_guid = System::createGUID(32); $conv_guid = System::createUUID();
$convuri = $recip_handle . ':' . $conv_guid; $convuri = $recip_handle . ':' . $conv_guid;
$handles = $recip_handle . ';' . $sender_handle; $handles = $recip_handle . ';' . $sender_handle;
@ -171,7 +171,7 @@ class Mail
$subject = L10n::t('[no subject]'); $subject = L10n::t('[no subject]');
} }
$guid = System::createGUID(32); $guid = System::createUUID();
$uri = 'urn:X-dfrn:' . System::baseUrl() . ':' . local_user() . ':' . $guid; $uri = 'urn:X-dfrn:' . System::baseUrl() . ':' . local_user() . ':' . $guid;
$me = Probe::uri($replyto); $me = Probe::uri($replyto);
@ -180,7 +180,7 @@ class Mail
return -2; return -2;
} }
$conv_guid = System::createGUID(32); $conv_guid = System::createUUID();
$recip_handle = $recipient['nickname'] . '@' . substr(System::baseUrl(), strpos(System::baseUrl(), '://') + 3); $recip_handle = $recipient['nickname'] . '@' . substr(System::baseUrl(), strpos(System::baseUrl(), '://') + 3);

View file

@ -28,6 +28,19 @@ require_once 'include/dba.php';
class Profile class Profile
{ {
/**
* @brief Returns default profile for a given user id
*
* @param integer User ID
*
* @return array Profile data
*/
public static function getByUID($uid)
{
$profile = DBA::selectFirst('profile', [], ['uid' => $uid, 'is-default' => true]);
return $profile;
}
/** /**
* @brief Returns a formatted location string from the given profile array * @brief Returns a formatted location string from the given profile array
* *

View file

@ -33,6 +33,17 @@ class Term
return $tag_text; return $tag_text;
} }
public static function tagArrayFromItemId($itemid, $type = [TERM_HASHTAG, TERM_MENTION])
{
$condition = ['otype' => TERM_OBJ_POST, 'oid' => $itemid, 'type' => $type];
$tags = DBA::select('term', ['type', 'term', 'url'], $condition);
if (!DBA::isResult($tags)) {
return [];
}
return DBA::toArray($tags);
}
public static function fileTextFromItemId($itemid) public static function fileTextFromItemId($itemid)
{ {
$file_text = ''; $file_text = '';
@ -99,6 +110,18 @@ class Term
$pattern = '/\W([\#@])\[url\=(.*?)\](.*?)\[\/url\]/ism'; $pattern = '/\W([\#@])\[url\=(.*?)\](.*?)\[\/url\]/ism';
if (preg_match_all($pattern, $data, $matches, PREG_SET_ORDER)) { if (preg_match_all($pattern, $data, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) { foreach ($matches as $match) {
if ($match[1] == '@') {
$contact = Contact::getDetailsByURL($match[2], 0);
if (!empty($contact['addr'])) {
$match[3] = $contact['addr'];
}
if (!empty($contact['url'])) {
$match[2] = $contact['url'];
}
}
$tags[$match[1] . trim($match[3], ',.:;[]/\"?!')] = $match[2]; $tags[$match[1] . trim($match[3], ',.:;[]/\"?!')] = $match[2];
} }
} }
@ -119,12 +142,22 @@ class Term
$term = substr($tag, 1); $term = substr($tag, 1);
} elseif (substr(trim($tag), 0, 1) == '@') { } elseif (substr(trim($tag), 0, 1) == '@') {
$type = TERM_MENTION; $type = TERM_MENTION;
$contact = Contact::getDetailsByURL($link, 0);
if (!empty($contact['name'])) {
$term = $contact['name'];
} else {
$term = substr($tag, 1); $term = substr($tag, 1);
}
} else { // This shouldn't happen } else { // This shouldn't happen
$type = TERM_HASHTAG; $type = TERM_HASHTAG;
$term = $tag; $term = $tag;
} }
if (DBA::exists('term', ['uid' => $message['uid'], 'otype' => TERM_OBJ_POST, 'oid' => $itemid, 'url' => $link])) {
continue;
}
if ($message['uid'] == 0) { if ($message['uid'] == 0) {
$global = true; $global = true;
DBA::update('term', ['global' => true], ['otype' => TERM_OBJ_POST, 'guid' => $message['guid']]); DBA::update('term', ['global' => true], ['otype' => TERM_OBJ_POST, 'guid' => $message['guid']]);

View file

@ -31,6 +31,23 @@ require_once 'include/text.php';
*/ */
class User class User
{ {
/**
* @brief Returns the user id of a given profile url
*
* @param string $profile
*
* @return integer user id
*/
public static function getIdForURL($url)
{
$self = DBA::selectFirst('contact', ['uid'], ['nurl' => normalise_link($url), 'self' => true]);
if (!DBA::isResult($self)) {
return false;
} else {
return $self['uid'];
}
}
/** /**
* @brief Get owner data by user id * @brief Get owner data by user id
* *
@ -495,7 +512,7 @@ class User
$spubkey = $sres['pubkey']; $spubkey = $sres['pubkey'];
$insert_result = DBA::insert('user', [ $insert_result = DBA::insert('user', [
'guid' => System::createGUID(32), 'guid' => System::createUUID(),
'username' => $username, 'username' => $username,
'password' => $new_password_encoded, 'password' => $new_password_encoded,
'email' => $email, 'email' => $email,

38
src/Module/Followers.php Normal file
View file

@ -0,0 +1,38 @@
<?php
/**
* @file src/Module/Followers.php
*/
namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\Protocol\ActivityPub;
use Friendica\Core\System;
use Friendica\Model\User;
/**
* ActivityPub Followers
*/
class Followers extends BaseModule
{
public static function rawContent()
{
$a = self::getApp();
if (empty($a->argv[1])) {
System::httpExit(404);
}
$owner = User::getOwnerDataByNick($a->argv[1]);
if (empty($owner)) {
System::httpExit(404);
}
$page = defaults($_REQUEST, 'page', null);
$followers = ActivityPub::getFollowers($owner, $page);
header('Content-Type: application/activity+json');
echo json_encode($followers);
exit();
}
}

38
src/Module/Following.php Normal file
View file

@ -0,0 +1,38 @@
<?php
/**
* @file src/Module/Following.php
*/
namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\Protocol\ActivityPub;
use Friendica\Core\System;
use Friendica\Model\User;
/**
* ActivityPub Following
*/
class Following extends BaseModule
{
public static function rawContent()
{
$a = self::getApp();
if (empty($a->argv[1])) {
System::httpExit(404);
}
$owner = User::getOwnerDataByNick($a->argv[1]);
if (empty($owner)) {
System::httpExit(404);
}
$page = defaults($_REQUEST, 'page', null);
$Following = ActivityPub::getFollowing($owner, $page);
header('Content-Type: application/activity+json');
echo json_encode($Following);
exit();
}
}

55
src/Module/Inbox.php Normal file
View file

@ -0,0 +1,55 @@
<?php
/**
* @file src/Module/Inbox.php
*/
namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\Protocol\ActivityPub;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\Util\HTTPSignature;
/**
* ActivityPub Inbox
*/
class Inbox extends BaseModule
{
public static function rawContent()
{
$a = self::getApp();
$postdata = file_get_contents('php://input');
if (empty($postdata)) {
System::httpExit(400);
}
// Enable for test purposes
/*
if (HTTPSignature::getSigner($postdata, $_SERVER)) {
$filename = 'signed-activitypub';
} else {
$filename = 'failed-activitypub';
}
$tempfile = tempnam(get_temppath(), $filename);
file_put_contents($tempfile, json_encode(['argv' => $a->argv, 'header' => $_SERVER, 'body' => $postdata], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
logger('Incoming message stored under ' . $tempfile);
*/
if (!empty($a->argv[1])) {
$user = DBA::selectFirst('user', ['uid'], ['nickname' => $a->argv[1]]);
if (!DBA::isResult($user)) {
System::httpExit(404);
}
$uid = $user['uid'];
} else {
$uid = 0;
}
ActivityPub::processInbox($postdata, $_SERVER, $uid);
System::httpExit(202);
}
}

View file

@ -76,13 +76,9 @@ class Magic extends BaseModule
// Create a header that is signed with the local users private key. // Create a header that is signed with the local users private key.
$headers = HTTPSignature::createSig( $headers = HTTPSignature::createSig(
'',
$headers, $headers,
$user['prvkey'], $user['prvkey'],
'acct:' . $user['nickname'] . '@' . $a->get_hostname() . ($a->urlpath ? '/' . $a->urlpath : ''), 'acct:' . $user['nickname'] . '@' . $a->get_hostname() . ($a->urlpath ? '/' . $a->urlpath : '')
false,
true,
'sha512'
); );
// Try to get an authentication token from the other instance. // Try to get an authentication token from the other instance.

41
src/Module/Object.php Normal file
View file

@ -0,0 +1,41 @@
<?php
/**
* @file src/Module/Object.php
*/
namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\Protocol\ActivityPub;
use Friendica\Core\System;
use Friendica\Model\Item;
use Friendica\Database\DBA;
/**
* ActivityPub Object
*/
class Object extends BaseModule
{
public static function rawContent()
{
$a = self::getApp();
if (empty($a->argv[1])) {
System::httpExit(404);
}
if (!ActivityPub::isRequest()) {
goaway(str_replace('object/', 'display/', $a->query_string));
}
$item = Item::selectFirst(['id'], ['guid' => $a->argv[1], 'wall' => true, 'private' => false]);
if (!DBA::isResult($item)) {
System::httpExit(404);
}
$data = ActivityPub::createObjectFromItemID($item['id']);
header('Content-Type: application/activity+json');
echo json_encode($data);
exit();
}
}

38
src/Module/Outbox.php Normal file
View file

@ -0,0 +1,38 @@
<?php
/**
* @file src/Module/Outbox.php
*/
namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\Protocol\ActivityPub;
use Friendica\Core\System;
use Friendica\Model\User;
/**
* ActivityPub Outbox
*/
class Outbox extends BaseModule
{
public static function rawContent()
{
$a = self::getApp();
if (empty($a->argv[1])) {
System::httpExit(404);
}
$owner = User::getOwnerDataByNick($a->argv[1]);
if (empty($owner)) {
System::httpExit(404);
}
$page = defaults($_REQUEST, 'page', null);
$outbox = ActivityPub::getOutbox($owner, $page);
header('Content-Type: application/activity+json');
echo json_encode($outbox);
exit();
}
}

View file

@ -54,7 +54,7 @@ class Owa extends BaseModule
if (DBA::isResult($contact)) { if (DBA::isResult($contact)) {
// Try to verify the signed header with the public key of the contact record // Try to verify the signed header with the public key of the contact record
// we have found. // we have found.
$verified = HTTPSignature::verify('', $contact['pubkey']); $verified = HTTPSignature::verifyMagic($contact['pubkey']);
if ($verified && $verified['header_signed'] && $verified['header_valid']) { if ($verified && $verified['header_signed'] && $verified['header_valid']) {
logger('OWA header: ' . print_r($verified, true), LOGGER_DATA); logger('OWA header: ' . print_r($verified, true), LOGGER_DATA);

View file

@ -19,6 +19,7 @@ use Friendica\Model\Contact;
use Friendica\Model\Profile; use Friendica\Model\Profile;
use Friendica\Protocol\Email; use Friendica\Protocol\Email;
use Friendica\Protocol\Feed; use Friendica\Protocol\Feed;
use Friendica\Protocol\ActivityPub;
use Friendica\Util\Crypto; use Friendica\Util\Crypto;
use Friendica\Util\DateTimeFormat; use Friendica\Util\DateTimeFormat;
use Friendica\Util\Network; use Friendica\Util\Network;
@ -328,7 +329,17 @@ class Probe
$uid = local_user(); $uid = local_user();
} }
if ($network != Protocol::ACTIVITYPUB) {
$data = self::detect($uri, $network, $uid); $data = self::detect($uri, $network, $uid);
} else {
$data = null;
}
$ap_profile = ActivityPub::probeProfile($uri);
if (!empty($ap_profile) && (defaults($data, 'network', '') != Protocol::DFRN)) {
$data = $ap_profile;
}
if (!isset($data["url"])) { if (!isset($data["url"])) {
$data["url"] = $uri; $data["url"] = $uri;

View file

@ -324,7 +324,7 @@ class Post extends BaseObject
$owner_name_e = $this->getOwnerName(); $owner_name_e = $this->getOwnerName();
// Disable features that aren't available in several networks // Disable features that aren't available in several networks
if (!in_array($item["network"], [Protocol::DFRN, Protocol::DIASPORA]) && isset($buttons["dislike"])) { if (!in_array($item["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA]) && isset($buttons["dislike"])) {
unset($buttons["dislike"]); unset($buttons["dislike"]);
$isevent = false; $isevent = false;
$tagger = ''; $tagger = '';

1944
src/Protocol/ActivityPub.php Normal file
View file

@ -0,0 +1,1944 @@
<?php
/**
* @file src/Protocol/ActivityPub.php
*/
namespace Friendica\Protocol;
use Friendica\Database\DBA;
use Friendica\Core\System;
use Friendica\BaseObject;
use Friendica\Util\Network;
use Friendica\Util\HTTPSignature;
use Friendica\Core\Protocol;
use Friendica\Model\Conversation;
use Friendica\Model\Contact;
use Friendica\Model\APContact;
use Friendica\Model\Item;
use Friendica\Model\Profile;
use Friendica\Model\Term;
use Friendica\Model\User;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Crypto;
use Friendica\Content\Text\BBCode;
use Friendica\Content\Text\HTML;
use Friendica\Util\JsonLD;
use Friendica\Util\LDSignature;
use Friendica\Core\Config;
/**
* @brief ActivityPub Protocol class
* The ActivityPub Protocol is a message exchange protocol defined by the W3C.
* https://www.w3.org/TR/activitypub/
* https://www.w3.org/TR/activitystreams-core/
* https://www.w3.org/TR/activitystreams-vocabulary/
*
* https://blog.joinmastodon.org/2018/06/how-to-implement-a-basic-activitypub-server/
* https://blog.joinmastodon.org/2018/07/how-to-make-friends-and-verify-requests/
*
* Digest: https://tools.ietf.org/html/rfc5843
* https://tools.ietf.org/html/draft-cavage-http-signatures-10#ref-15
*
* Mastodon implementation of supported activities:
* https://github.com/tootsuite/mastodon/blob/master/app/lib/activitypub/activity.rb#L26
*
* To-do:
*
* Receiver:
* - Update (Image, Video, Article, Note)
* - Event
* - Undo Announce
*
* Check what this is meant to do:
* - Add
* - Block
* - Flag
* - Remove
* - Undo Block
* - Undo Accept (Problem: This could invert a contact accept or an event accept)
*
* Transmitter:
* - Event
*
* Complicated:
* - Announce
* - Undo Announce
*
* General:
* - Attachments
* - nsfw (sensitive)
* - Queueing unsucessful deliveries
* - Polling the outboxes for missing content?
* - Possibly using the LD-JSON parser
*/
class ActivityPub
{
const PUBLIC_COLLECTION = 'https://www.w3.org/ns/activitystreams#Public';
const CONTEXT = ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1',
['vcard' => 'http://www.w3.org/2006/vcard/ns#',
'diaspora' => 'https://diasporafoundation.org/ns/',
'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers',
'sensitive' => 'as:sensitive', 'Hashtag' => 'as:Hashtag']];
const ACCOUNT_TYPES = ['Person', 'Organization', 'Service', 'Group', 'Application'];
const CONTENT_TYPES = ['Note', 'Article', 'Video', 'Image'];
const ACTIVITY_TYPES = ['Like', 'Dislike', 'Accept', 'Reject', 'TentativeAccept'];
/**
* @brief Checks if the web request is done for the AP protocol
*
* @return is it AP?
*/
public static function isRequest()
{
return stristr(defaults($_SERVER, 'HTTP_ACCEPT', ''), 'application/activity+json') ||
stristr(defaults($_SERVER, 'HTTP_ACCEPT', ''), 'application/ld+json');
}
/**
* @brief collects the lost of followers of the given owner
*
* @param array $owner Owner array
* @param integer $page Page number
*
* @return array of owners
*/
public static function getFollowers($owner, $page = null)
{
$condition = ['rel' => [Contact::FOLLOWER, Contact::FRIEND], 'network' => Protocol::NATIVE_SUPPORT, 'uid' => $owner['uid'],
'self' => false, 'hidden' => false, 'archive' => false, 'pending' => false];
$count = DBA::count('contact', $condition);
$data = ['@context' => self::CONTEXT];
$data['id'] = System::baseUrl() . '/followers/' . $owner['nickname'];
$data['type'] = 'OrderedCollection';
$data['totalItems'] = $count;
// When we hide our friends we will only show the pure number but don't allow more.
$profile = Profile::getByUID($owner['uid']);
if (!empty($profile['hide-friends'])) {
return $data;
}
if (empty($page)) {
$data['first'] = System::baseUrl() . '/followers/' . $owner['nickname'] . '?page=1';
} else {
$list = [];
$contacts = DBA::select('contact', ['url'], $condition, ['limit' => [($page - 1) * 100, 100]]);
while ($contact = DBA::fetch($contacts)) {
$list[] = $contact['url'];
}
if (!empty($list)) {
$data['next'] = System::baseUrl() . '/followers/' . $owner['nickname'] . '?page=' . ($page + 1);
}
$data['partOf'] = System::baseUrl() . '/followers/' . $owner['nickname'];
$data['orderedItems'] = $list;
}
return $data;
}
/**
* @brief Create list of following contacts
*
* @param array $owner Owner array
* @param integer $page Page numbe
*
* @return array of following contacts
*/
public static function getFollowing($owner, $page = null)
{
$condition = ['rel' => [Contact::SHARING, Contact::FRIEND], 'network' => Protocol::NATIVE_SUPPORT, 'uid' => $owner['uid'],
'self' => false, 'hidden' => false, 'archive' => false, 'pending' => false];
$count = DBA::count('contact', $condition);
$data = ['@context' => self::CONTEXT];
$data['id'] = System::baseUrl() . '/following/' . $owner['nickname'];
$data['type'] = 'OrderedCollection';
$data['totalItems'] = $count;
// When we hide our friends we will only show the pure number but don't allow more.
$profile = Profile::getByUID($owner['uid']);
if (!empty($profile['hide-friends'])) {
return $data;
}
if (empty($page)) {
$data['first'] = System::baseUrl() . '/following/' . $owner['nickname'] . '?page=1';
} else {
$list = [];
$contacts = DBA::select('contact', ['url'], $condition, ['limit' => [($page - 1) * 100, 100]]);
while ($contact = DBA::fetch($contacts)) {
$list[] = $contact['url'];
}
if (!empty($list)) {
$data['next'] = System::baseUrl() . '/following/' . $owner['nickname'] . '?page=' . ($page + 1);
}
$data['partOf'] = System::baseUrl() . '/following/' . $owner['nickname'];
$data['orderedItems'] = $list;
}
return $data;
}
/**
* @brief Public posts for the given owner
*
* @param array $owner Owner array
* @param integer $page Page numbe
*
* @return array of posts
*/
public static function getOutbox($owner, $page = null)
{
$public_contact = Contact::getIdForURL($owner['url'], 0, true);
$condition = ['uid' => $owner['uid'], 'contact-id' => $owner['id'], 'author-id' => $public_contact,
'wall' => true, 'private' => false, 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT],
'deleted' => false, 'visible' => true];
$count = DBA::count('item', $condition);
$data = ['@context' => self::CONTEXT];
$data['id'] = System::baseUrl() . '/outbox/' . $owner['nickname'];
$data['type'] = 'OrderedCollection';
$data['totalItems'] = $count;
if (empty($page)) {
$data['first'] = System::baseUrl() . '/outbox/' . $owner['nickname'] . '?page=1';
} else {
$list = [];
$condition['parent-network'] = Protocol::NATIVE_SUPPORT;
$items = Item::select(['id'], $condition, ['limit' => [($page - 1) * 20, 20], 'order' => ['created' => true]]);
while ($item = Item::fetch($items)) {
$object = self::createObjectFromItemID($item['id']);
unset($object['@context']);
$list[] = $object;
}
if (!empty($list)) {
$data['next'] = System::baseUrl() . '/outbox/' . $owner['nickname'] . '?page=' . ($page + 1);
}
$data['partOf'] = System::baseUrl() . '/outbox/' . $owner['nickname'];
$data['orderedItems'] = $list;
}
return $data;
}
/**
* Return the ActivityPub profile of the given user
*
* @param integer $uid User ID
* @return profile array
*/
public static function profile($uid)
{
$condition = ['uid' => $uid, 'blocked' => false, 'account_expired' => false,
'account_removed' => false, 'verified' => true];
$fields = ['guid', 'nickname', 'pubkey', 'account-type', 'page-flags'];
$user = DBA::selectFirst('user', $fields, $condition);
if (!DBA::isResult($user)) {
return [];
}
$fields = ['locality', 'region', 'country-name'];
$profile = DBA::selectFirst('profile', $fields, ['uid' => $uid, 'is-default' => true]);
if (!DBA::isResult($profile)) {
return [];
}
$fields = ['name', 'url', 'location', 'about', 'avatar'];
$contact = DBA::selectFirst('contact', $fields, ['uid' => $uid, 'self' => true]);
if (!DBA::isResult($contact)) {
return [];
}
$data = ['@context' => self::CONTEXT];
$data['id'] = $contact['url'];
$data['diaspora:guid'] = $user['guid'];
$data['type'] = self::ACCOUNT_TYPES[$user['account-type']];
$data['following'] = System::baseUrl() . '/following/' . $user['nickname'];
$data['followers'] = System::baseUrl() . '/followers/' . $user['nickname'];
$data['inbox'] = System::baseUrl() . '/inbox/' . $user['nickname'];
$data['outbox'] = System::baseUrl() . '/outbox/' . $user['nickname'];
$data['preferredUsername'] = $user['nickname'];
$data['name'] = $contact['name'];
$data['vcard:hasAddress'] = ['@type' => 'vcard:Home', 'vcard:country-name' => $profile['country-name'],
'vcard:region' => $profile['region'], 'vcard:locality' => $profile['locality']];
$data['summary'] = $contact['about'];
$data['url'] = $contact['url'];
$data['manuallyApprovesFollowers'] = in_array($user['page-flags'], [Contact::PAGE_NORMAL, Contact::PAGE_PRVGROUP]);
$data['publicKey'] = ['id' => $contact['url'] . '#main-key',
'owner' => $contact['url'],
'publicKeyPem' => $user['pubkey']];
$data['endpoints'] = ['sharedInbox' => System::baseUrl() . '/inbox'];
$data['icon'] = ['type' => 'Image',
'url' => $contact['avatar']];
// tags: https://kitty.town/@inmysocks/100656097926961126.json
return $data;
}
/**
* @brief Returns an array with permissions of a given item array
*
* @param array $item
*
* @return array with permissions
*/
private static function fetchPermissionBlockFromConversation($item)
{
if (empty($item['thr-parent'])) {
return [];
}
$condition = ['item-uri' => $item['thr-parent'], 'protocol' => Conversation::PARCEL_ACTIVITYPUB];
$conversation = DBA::selectFirst('conversation', ['source'], $condition);
if (!DBA::isResult($conversation)) {
return [];
}
$activity = json_decode($conversation['source'], true);
$actor = JsonLD::fetchElement($activity, 'actor', 'id');
$profile = APContact::getByURL($actor);
$item_profile = APContact::getByURL($item['author-link']);
$exclude[] = $item['author-link'];
if ($item['gravity'] == GRAVITY_PARENT) {
$exclude[] = $item['owner-link'];
}
$permissions['to'][] = $actor;
foreach (['to', 'cc', 'bto', 'bcc'] as $element) {
if (empty($activity[$element])) {
continue;
}
if (is_string($activity[$element])) {
$activity[$element] = [$activity[$element]];
}
foreach ($activity[$element] as $receiver) {
if ($receiver == $profile['followers'] && !empty($item_profile['followers'])) {
$receiver = $item_profile['followers'];
}
if (!in_array($receiver, $exclude)) {
$permissions[$element][] = $receiver;
}
}
}
return $permissions;
}
/**
* @brief Creates an array of permissions from an item thread
*
* @param array $item
*
* @return permission array
*/
public static function createPermissionBlockForItem($item)
{
$data = ['to' => [], 'cc' => []];
$data = array_merge($data, self::fetchPermissionBlockFromConversation($item));
$actor_profile = APContact::getByURL($item['author-link']);
$terms = Term::tagArrayFromItemId($item['id'], TERM_MENTION);
$contacts[$item['author-link']] = $item['author-link'];
if (!$item['private']) {
$data['to'][] = self::PUBLIC_COLLECTION;
if (!empty($actor_profile['followers'])) {
$data['cc'][] = $actor_profile['followers'];
}
foreach ($terms as $term) {
$profile = APContact::getByURL($term['url'], false);
if (!empty($profile) && empty($contacts[$profile['url']])) {
$data['cc'][] = $profile['url'];
$contacts[$profile['url']] = $profile['url'];
}
}
} else {
$receiver_list = Item::enumeratePermissions($item);
$mentioned = [];
foreach ($terms as $term) {
$cid = Contact::getIdForURL($term['url'], $item['uid']);
if (!empty($cid) && in_array($cid, $receiver_list)) {
$contact = DBA::selectFirst('contact', ['url'], ['id' => $cid, 'network' => Protocol::ACTIVITYPUB]);
$data['to'][] = $contact['url'];
$contacts[$contact['url']] = $contact['url'];
}
}
foreach ($receiver_list as $receiver) {
$contact = DBA::selectFirst('contact', ['url'], ['id' => $receiver, 'network' => Protocol::ACTIVITYPUB]);
if (empty($contacts[$contact['url']])) {
$data['cc'][] = $contact['url'];
$contacts[$contact['url']] = $contact['url'];
}
}
}
$parents = Item::select(['id', 'author-link', 'owner-link', 'gravity'], ['parent' => $item['parent']]);
while ($parent = Item::fetch($parents)) {
// Don't include data from future posts
if ($parent['id'] >= $item['id']) {
continue;
}
$profile = APContact::getByURL($parent['author-link'], false);
if (!empty($profile) && empty($contacts[$profile['url']])) {
$data['cc'][] = $profile['url'];
$contacts[$profile['url']] = $profile['url'];
}
if ($item['gravity'] != GRAVITY_PARENT) {
continue;
}
$profile = APContact::getByURL($parent['owner-link'], false);
if (!empty($profile) && empty($contacts[$profile['url']])) {
$data['cc'][] = $profile['url'];
$contacts[$profile['url']] = $profile['url'];
}
}
DBA::close($parents);
if (empty($data['to'])) {
$data['to'] = $data['cc'];
$data['cc'] = [];
}
return $data;
}
/**
* @brief Fetches a list of inboxes of followers of a given user
*
* @param integer $uid User ID
*
* @return array of follower inboxes
*/
public static function fetchTargetInboxesforUser($uid)
{
$inboxes = [];
$condition = ['uid' => $uid, 'network' => Protocol::ACTIVITYPUB, 'archive' => false, 'pending' => false];
if (!empty($uid)) {
$condition['rel'] = [Contact::FOLLOWER, Contact::FRIEND];
}
$contacts = DBA::select('contact', ['notify', 'batch'], $condition);
while ($contact = DBA::fetch($contacts)) {
$contact = defaults($contact, 'batch', $contact['notify']);
$inboxes[$contact] = $contact;
}
DBA::close($contacts);
return $inboxes;
}
/**
* @brief Fetches an array of inboxes for the given item and user
*
* @param array $item
* @param integer $uid User ID
*
* @return array with inboxes
*/
public static function fetchTargetInboxes($item, $uid)
{
$permissions = self::createPermissionBlockForItem($item);
if (empty($permissions)) {
return [];
}
$inboxes = [];
if ($item['gravity'] == GRAVITY_ACTIVITY) {
$item_profile = APContact::getByURL($item['author-link']);
} else {
$item_profile = APContact::getByURL($item['owner-link']);
}
foreach (['to', 'cc', 'bto', 'bcc'] as $element) {
if (empty($permissions[$element])) {
continue;
}
foreach ($permissions[$element] as $receiver) {
if ($receiver == $item_profile['followers']) {
$inboxes = self::fetchTargetInboxesforUser($uid);
} else {
$profile = APContact::getByURL($receiver);
if (!empty($profile)) {
$target = defaults($profile, 'sharedinbox', $profile['inbox']);
$inboxes[$target] = $target;
}
}
}
}
return $inboxes;
}
/**
* @brief Returns the activity type of a given item
*
* @param array $item
*
* @return activity type
*/
public static function getTypeOfItem($item)
{
if ($item['verb'] == ACTIVITY_POST) {
if ($item['created'] == $item['edited']) {
$type = 'Create';
} else {
$type = 'Update';
}
} elseif ($item['verb'] == ACTIVITY_LIKE) {
$type = 'Like';
} elseif ($item['verb'] == ACTIVITY_DISLIKE) {
$type = 'Dislike';
} elseif ($item['verb'] == ACTIVITY_ATTEND) {
$type = 'Accept';
} elseif ($item['verb'] == ACTIVITY_ATTENDNO) {
$type = 'Reject';
} elseif ($item['verb'] == ACTIVITY_ATTENDMAYBE) {
$type = 'TentativeAccept';
} else {
$type = '';
}
return $type;
}
/**
* @brief Creates an activity array for a given item id
*
* @param integer $item_id
* @param boolean $object_mode Is the activity item is used inside another object?
*
* @return array of activity
*/
public static function createActivityFromItem($item_id, $object_mode = false)
{
$item = Item::selectFirst([], ['id' => $item_id, 'parent-network' => Protocol::NATIVE_SUPPORT]);
if (!DBA::isResult($item)) {
return false;
}
$condition = ['item-uri' => $item['uri'], 'protocol' => Conversation::PARCEL_ACTIVITYPUB];
$conversation = DBA::selectFirst('conversation', ['source'], $condition);
if (DBA::isResult($conversation)) {
$data = json_decode($conversation['source']);
if (!empty($data)) {
return $data;
}
}
$type = self::getTypeOfItem($item);
if (!$object_mode) {
$data = ['@context' => self::CONTEXT];
if ($item['deleted'] && ($item['gravity'] == GRAVITY_ACTIVITY)) {
$type = 'Undo';
} elseif ($item['deleted']) {
$type = 'Delete';
}
} else {
$data = [];
}
$data['id'] = $item['uri'] . '#' . $type;
$data['type'] = $type;
$data['actor'] = $item['author-link'];
$data['published'] = DateTimeFormat::utc($item["created"]."+00:00", DateTimeFormat::ATOM);
if ($item["created"] != $item["edited"]) {
$data['updated'] = DateTimeFormat::utc($item["edited"]."+00:00", DateTimeFormat::ATOM);
}
$data['context'] = self::fetchContextURLForItem($item);
$data = array_merge($data, ActivityPub::createPermissionBlockForItem($item));
if (in_array($data['type'], ['Create', 'Update', 'Announce', 'Delete'])) {
$data['object'] = self::createNote($item);
} elseif ($data['type'] == 'Undo') {
$data['object'] = self::createActivityFromItem($item_id, true);
} else {
$data['object'] = $item['thr-parent'];
}
$owner = User::getOwnerDataById($item['uid']);
if (!$object_mode) {
return LDSignature::sign($data, $owner);
} else {
return $data;
}
}
/**
* @brief Creates an object array for a given item id
*
* @param integer $item_id
*
* @return object array
*/
public static function createObjectFromItemID($item_id)
{
$item = Item::selectFirst([], ['id' => $item_id, 'parent-network' => Protocol::NATIVE_SUPPORT]);
if (!DBA::isResult($item)) {
return false;
}
$data = ['@context' => self::CONTEXT];
$data = array_merge($data, self::createNote($item));
return $data;
}
/**
* @brief Returns a tag array for a given item array
*
* @param array $item
*
* @return array of tags
*/
private static function createTagList($item)
{
$tags = [];
$terms = Term::tagArrayFromItemId($item['id'], TERM_MENTION);
foreach ($terms as $term) {
$contact = Contact::getDetailsByURL($term['url']);
if (!empty($contact['addr'])) {
$mention = '@' . $contact['addr'];
} else {
$mention = '@' . $term['url'];
}
$tags[] = ['type' => 'Mention', 'href' => $term['url'], 'name' => $mention];
}
return $tags;
}
/**
* @brief Fetches the "context" value for a givem item array from the "conversation" table
*
* @param array $item
*
* @return string with context url
*/
private static function fetchContextURLForItem($item)
{
$conversation = DBA::selectFirst('conversation', ['conversation-href', 'conversation-uri'], ['item-uri' => $item['parent-uri']]);
if (DBA::isResult($conversation) && !empty($conversation['conversation-href'])) {
$context_uri = $conversation['conversation-href'];
} elseif (DBA::isResult($conversation) && !empty($conversation['conversation-uri'])) {
$context_uri = $conversation['conversation-uri'];
} else {
$context_uri = str_replace('/object/', '/context/', $item['parent-uri']);
}
return $context_uri;
}
/**
* @brief Creates a note/article object array
*
* @param array $item
*
* @return object array
*/
private static function createNote($item)
{
if (!empty($item['title'])) {
$type = 'Article';
} else {
$type = 'Note';
}
if ($item['deleted']) {
$type = 'Tombstone';
}
$data = [];
$data['id'] = $item['uri'];
$data['type'] = $type;
if ($item['deleted']) {
return $data;
}
$data['summary'] = null; // Ignore by now
if ($item['uri'] != $item['thr-parent']) {
$data['inReplyTo'] = $item['thr-parent'];
} else {
$data['inReplyTo'] = null;
}
$data['diaspora:guid'] = $item['guid'];
$data['published'] = DateTimeFormat::utc($item["created"]."+00:00", DateTimeFormat::ATOM);
if ($item["created"] != $item["edited"]) {
$data['updated'] = DateTimeFormat::utc($item["edited"]."+00:00", DateTimeFormat::ATOM);
}
$data['url'] = $item['plink'];
$data['attributedTo'] = $item['author-link'];
$data['actor'] = $item['author-link'];
$data['sensitive'] = false; // - Query NSFW
$data['context'] = self::fetchContextURLForItem($item);
if (!empty($item['title'])) {
$data['name'] = BBCode::convert($item['title'], false, 7);
}
$data['content'] = BBCode::convert($item['body'], false, 7);
$data['source'] = ['content' => $item['body'], 'mediaType' => "text/bbcode"];
if (!empty($item['signed_text']) && ($item['uri'] != $item['thr-parent'])) {
$data['diaspora:comment'] = $item['signed_text'];
}
$data['attachment'] = []; // @ToDo
$data['tag'] = self::createTagList($item);
$data = array_merge($data, ActivityPub::createPermissionBlockForItem($item));
return $data;
}
/**
* @brief Transmits a profile deletion to a given inbox
*
* @param integer $uid User ID
* @param string $inbox Target inbox
*/
public static function transmitProfileDeletion($uid, $inbox)
{
$owner = User::getOwnerDataById($uid);
$profile = APContact::getByURL($owner['url']);
$data = ['@context' => 'https://www.w3.org/ns/activitystreams',
'id' => System::baseUrl() . '/activity/' . System::createGUID(),
'type' => 'Delete',
'actor' => $owner['url'],
'object' => self::profile($uid),
'published' => DateTimeFormat::utcNow(DateTimeFormat::ATOM),
'to' => [self::PUBLIC_COLLECTION],
'cc' => []];
$signed = LDSignature::sign($data, $owner);
logger('Deliver profile deletion for user ' . $uid . ' to ' . $inbox .' via ActivityPub', LOGGER_DEBUG);
HTTPSignature::transmit($signed, $inbox, $uid);
}
/**
* @brief Transmits a profile change to a given inbox
*
* @param integer $uid User ID
* @param string $inbox Target inbox
*/
public static function transmitProfileUpdate($uid, $inbox)
{
$owner = User::getOwnerDataById($uid);
$profile = APContact::getByURL($owner['url']);
$data = ['@context' => 'https://www.w3.org/ns/activitystreams',
'id' => System::baseUrl() . '/activity/' . System::createGUID(),
'type' => 'Update',
'actor' => $owner['url'],
'object' => self::profile($uid),
'published' => DateTimeFormat::utcNow(DateTimeFormat::ATOM),
'to' => [$profile['followers']],
'cc' => []];
$signed = LDSignature::sign($data, $owner);
logger('Deliver profile update for user ' . $uid . ' to ' . $inbox .' via ActivityPub', LOGGER_DEBUG);
HTTPSignature::transmit($signed, $inbox, $uid);
}
/**
* @brief Transmits a given activity to a target
*
* @param array $activity
* @param string $target Target profile
* @param integer $uid User ID
*/
public static function transmitActivity($activity, $target, $uid)
{
$profile = APContact::getByURL($target);
$owner = User::getOwnerDataById($uid);
$data = ['@context' => 'https://www.w3.org/ns/activitystreams',
'id' => System::baseUrl() . '/activity/' . System::createGUID(),
'type' => $activity,
'actor' => $owner['url'],
'object' => $profile['url'],
'to' => $profile['url']];
logger('Sending activity ' . $activity . ' to ' . $target . ' for user ' . $uid, LOGGER_DEBUG);
$signed = LDSignature::sign($data, $owner);
HTTPSignature::transmit($signed, $profile['inbox'], $uid);
}
/**
* @brief Transmit a message that the contact request had been accepted
*
* @param string $target Target profile
* @param $id
* @param integer $uid User ID
*/
public static function transmitContactAccept($target, $id, $uid)
{
$profile = APContact::getByURL($target);
$owner = User::getOwnerDataById($uid);
$data = ['@context' => 'https://www.w3.org/ns/activitystreams',
'id' => System::baseUrl() . '/activity/' . System::createGUID(),
'type' => 'Accept',
'actor' => $owner['url'],
'object' => ['id' => $id, 'type' => 'Follow',
'actor' => $profile['url'],
'object' => $owner['url']],
'to' => $profile['url']];
logger('Sending accept to ' . $target . ' for user ' . $uid . ' with id ' . $id, LOGGER_DEBUG);
$signed = LDSignature::sign($data, $owner);
HTTPSignature::transmit($signed, $profile['inbox'], $uid);
}
/**
* @brief
*
* @param string $target Target profile
* @param $id
* @param integer $uid User ID
*/
public static function transmitContactReject($target, $id, $uid)
{
$profile = APContact::getByURL($target);
$owner = User::getOwnerDataById($uid);
$data = ['@context' => 'https://www.w3.org/ns/activitystreams',
'id' => System::baseUrl() . '/activity/' . System::createGUID(),
'type' => 'Reject',
'actor' => $owner['url'],
'object' => ['id' => $id, 'type' => 'Follow',
'actor' => $profile['url'],
'object' => $owner['url']],
'to' => $profile['url']];
logger('Sending reject to ' . $target . ' for user ' . $uid . ' with id ' . $id, LOGGER_DEBUG);
$signed = LDSignature::sign($data, $owner);
HTTPSignature::transmit($signed, $profile['inbox'], $uid);
}
/**
* @brief
*
* @param string $target Target profile
* @param integer $uid User ID
*/
public static function transmitContactUndo($target, $uid)
{
$profile = APContact::getByURL($target);
$id = System::baseUrl() . '/activity/' . System::createGUID();
$owner = User::getOwnerDataById($uid);
$data = ['@context' => 'https://www.w3.org/ns/activitystreams',
'id' => $id,
'type' => 'Undo',
'actor' => $owner['url'],
'object' => ['id' => $id, 'type' => 'Follow',
'actor' => $owner['url'],
'object' => $profile['url']],
'to' => $profile['url']];
logger('Sending undo to ' . $target . ' for user ' . $uid . ' with id ' . $id, LOGGER_DEBUG);
$signed = LDSignature::sign($data, $owner);
HTTPSignature::transmit($signed, $profile['inbox'], $uid);
}
/**
* Fetches ActivityPub content from the given url
*
* @param string $url content url
* @return array
*/
public static function fetchContent($url)
{
$ret = Network::curl($url, false, $redirects, ['accept_content' => 'application/activity+json, application/ld+json']);
if (!$ret['success'] || empty($ret['body'])) {
return false;
}
return json_decode($ret['body'], true);
}
/**
* Fetches a profile from the given url into an array that is compatible to Probe::uri
*
* @param string $url profile url
* @return array
*/
public static function probeProfile($url)
{
$apcontact = APContact::getByURL($url, true);
if (empty($apcontact)) {
return false;
}
$profile = ['network' => Protocol::ACTIVITYPUB];
$profile['nick'] = $apcontact['nick'];
$profile['name'] = $apcontact['name'];
$profile['guid'] = $apcontact['uuid'];
$profile['url'] = $apcontact['url'];
$profile['addr'] = $apcontact['addr'];
$profile['alias'] = $apcontact['alias'];
$profile['photo'] = $apcontact['photo'];
// $profile['community']
// $profile['keywords']
// $profile['location']
$profile['about'] = $apcontact['about'];
$profile['batch'] = $apcontact['sharedinbox'];
$profile['notify'] = $apcontact['inbox'];
$profile['poll'] = $apcontact['outbox'];
$profile['pubkey'] = $apcontact['pubkey'];
$profile['baseurl'] = $apcontact['baseurl'];
// Remove all "null" fields
foreach ($profile as $field => $content) {
if (is_null($content)) {
unset($profile[$field]);
}
}
return $profile;
}
/**
* @brief
*
* @param $body
* @param $header
* @param integer $uid User ID
*/
public static function processInbox($body, $header, $uid)
{
$http_signer = HTTPSignature::getSigner($body, $header);
if (empty($http_signer)) {
logger('Invalid HTTP signature, message will be discarded.', LOGGER_DEBUG);
return;
} else {
logger('HTTP signature is signed by ' . $http_signer, LOGGER_DEBUG);
}
$activity = json_decode($body, true);
$actor = JsonLD::fetchElement($activity, 'actor', 'id');
logger('Message for user ' . $uid . ' is from actor ' . $actor, LOGGER_DEBUG);
if (empty($activity)) {
logger('Invalid body.', LOGGER_DEBUG);
return;
}
if (LDSignature::isSigned($activity)) {
$ld_signer = LDSignature::getSigner($activity);
if (empty($ld_signer)) {
logger('Invalid JSON-LD signature from ' . $actor, LOGGER_DEBUG);
}
if (!empty($ld_signer && ($actor == $http_signer))) {
logger('The HTTP and the JSON-LD signature belong to ' . $ld_signer, LOGGER_DEBUG);
$trust_source = true;
} elseif (!empty($ld_signer)) {
logger('JSON-LD signature is signed by ' . $ld_signer, LOGGER_DEBUG);
$trust_source = true;
} elseif ($actor == $http_signer) {
logger('Bad JSON-LD signature, but HTTP signer fits the actor.', LOGGER_DEBUG);
$trust_source = true;
} else {
logger('Invalid JSON-LD signature and the HTTP signer is different.', LOGGER_DEBUG);
$trust_source = false;
}
} elseif ($actor == $http_signer) {
logger('Trusting post without JSON-LD signature, The actor fits the HTTP signer.', LOGGER_DEBUG);
$trust_source = true;
} else {
logger('No JSON-LD signature, different actor.', LOGGER_DEBUG);
$trust_source = false;
}
self::processActivity($activity, $body, $uid, $trust_source);
}
/**
* @brief
*
* @param $url
* @param integer $uid User ID
*/
public static function fetchOutbox($url, $uid)
{
$data = self::fetchContent($url);
if (empty($data)) {
return;
}
if (!empty($data['orderedItems'])) {
$items = $data['orderedItems'];
} elseif (!empty($data['first']['orderedItems'])) {
$items = $data['first']['orderedItems'];
} elseif (!empty($data['first'])) {
self::fetchOutbox($data['first'], $uid);
return;
} else {
$items = [];
}
foreach ($items as $activity) {
self::processActivity($activity, '', $uid, true);
}
}
/**
* @brief
*
* @param array $activity
* @param integer $uid User ID
* @param $trust_source
*
* @return
*/
private static function prepareObjectData($activity, $uid, &$trust_source)
{
$actor = JsonLD::fetchElement($activity, 'actor', 'id');
if (empty($actor)) {
logger('Empty actor', LOGGER_DEBUG);
return [];
}
// Fetch all receivers from to, cc, bto and bcc
$receivers = self::getReceivers($activity, $actor);
// When it is a delivery to a personal inbox we add that user to the receivers
if (!empty($uid)) {
$owner = User::getOwnerDataById($uid);
$additional = ['uid:' . $uid => $uid];
$receivers = array_merge($receivers, $additional);
}
logger('Receivers: ' . json_encode($receivers), LOGGER_DEBUG);
$object_id = JsonLD::fetchElement($activity, 'object', 'id');
if (empty($object_id)) {
logger('No object found', LOGGER_DEBUG);
return [];
}
// Fetch the content only on activities where this matters
if (in_array($activity['type'], ['Create', 'Announce'])) {
$object_data = self::fetchObject($object_id, $activity['object'], $trust_source);
if (empty($object_data)) {
logger("Object data couldn't be processed", LOGGER_DEBUG);
return [];
}
// We had been able to retrieve the object data - so we can trust the source
$trust_source = true;
} elseif (in_array($activity['type'], ['Like', 'Dislike'])) {
// Create a mostly empty array out of the activity data (instead of the object).
// This way we later don't have to check for the existence of ech individual array element.
$object_data = self::processObject($activity);
$object_data['name'] = $activity['type'];
$object_data['author'] = $activity['actor'];
$object_data['object'] = $object_id;
$object_data['object_type'] = ''; // Since we don't fetch the object, we don't know the type
} else {
$object_data = [];
$object_data['id'] = $activity['id'];
$object_data['object'] = $activity['object'];
$object_data['object_type'] = JsonLD::fetchElement($activity, 'object', 'type');
}
$object_data = self::addActivityFields($object_data, $activity);
$object_data['type'] = $activity['type'];
$object_data['owner'] = $actor;
$object_data['receiver'] = array_merge(defaults($object_data, 'receiver', []), $receivers);
logger('Processing ' . $object_data['type'] . ' ' . $object_data['object_type'] . ' ' . $object_data['id'], LOGGER_DEBUG);
return $object_data;
}
/**
* @brief
*
* @param array $activity
* @param $body
* @param integer $uid User ID
* @param $trust_source
*/
private static function processActivity($activity, $body = '', $uid = null, $trust_source = false)
{
if (empty($activity['type'])) {
logger('Empty type', LOGGER_DEBUG);
return;
}
if (empty($activity['object'])) {
logger('Empty object', LOGGER_DEBUG);
return;
}
if (empty($activity['actor'])) {
logger('Empty actor', LOGGER_DEBUG);
return;
}
// $trust_source is called by reference and is set to true if the content was retrieved successfully
$object_data = self::prepareObjectData($activity, $uid, $trust_source);
if (empty($object_data)) {
logger('No object data found', LOGGER_DEBUG);
return;
}
if (!$trust_source) {
logger('No trust for activity type "' . $activity['type'] . '", so we quit now.', LOGGER_DEBUG);
}
switch ($activity['type']) {
case 'Create':
case 'Announce':
self::createItem($object_data, $body);
break;
case 'Like':
self::likeItem($object_data, $body);
break;
case 'Dislike':
self::dislikeItem($object_data, $body);
break;
case 'Update':
if (in_array($object_data['object_type'], self::CONTENT_TYPES)) {
/// @todo
} elseif (in_array($object_data['object_type'], self::ACCOUNT_TYPES)) {
self::updatePerson($object_data, $body);
}
break;
case 'Delete':
if ($object_data['object_type'] == 'Tombstone') {
self::deleteItem($object_data, $body);
} elseif (in_array($object_data['object_type'], self::ACCOUNT_TYPES)) {
self::deletePerson($object_data, $body);
}
break;
case 'Follow':
self::followUser($object_data);
break;
case 'Accept':
if ($object_data['object_type'] == 'Follow') {
self::acceptFollowUser($object_data);
}
break;
case 'Reject':
if ($object_data['object_type'] == 'Follow') {
self::rejectFollowUser($object_data);
}
break;
case 'Undo':
if ($object_data['object_type'] == 'Follow') {
self::undoFollowUser($object_data);
} elseif (in_array($object_data['object_type'], self::ACTIVITY_TYPES)) {
self::undoActivity($object_data);
}
break;
default:
logger('Unknown activity: ' . $activity['type'], LOGGER_DEBUG);
break;
}
}
/**
* @brief
*
* @param array $activity
* @param $actor
*
* @return
*/
private static function getReceivers($activity, $actor)
{
$receivers = [];
// When it is an answer, we inherite the receivers from the parent
$replyto = JsonLD::fetchElement($activity, 'inReplyTo', 'id');
if (!empty($replyto)) {
$parents = Item::select(['uid'], ['uri' => $replyto]);
while ($parent = Item::fetch($parents)) {
$receivers['uid:' . $parent['uid']] = $parent['uid'];
}
}
if (!empty($actor)) {
$profile = APContact::getByURL($actor);
$followers = defaults($profile, 'followers', '');
logger('Actor: ' . $actor . ' - Followers: ' . $followers, LOGGER_DEBUG);
} else {
logger('Empty actor', LOGGER_DEBUG);
$followers = '';
}
foreach (['to', 'cc', 'bto', 'bcc'] as $element) {
if (empty($activity[$element])) {
continue;
}
// The receiver can be an array or a string
if (is_string($activity[$element])) {
$activity[$element] = [$activity[$element]];
}
foreach ($activity[$element] as $receiver) {
if ($receiver == self::PUBLIC_COLLECTION) {
$receivers['uid:0'] = 0;
}
if (($receiver == self::PUBLIC_COLLECTION) && !empty($actor)) {
// This will most likely catch all OStatus connections to Mastodon
$condition = ['alias' => [$actor, normalise_link($actor)], 'rel' => [Contact::SHARING, Contact::FRIEND]
, 'archive' => false, 'pending' => false];
$contacts = DBA::select('contact', ['uid'], $condition);
while ($contact = DBA::fetch($contacts)) {
if ($contact['uid'] != 0) {
$receivers['uid:' . $contact['uid']] = $contact['uid'];
}
}
DBA::close($contacts);
}
if (in_array($receiver, [$followers, self::PUBLIC_COLLECTION]) && !empty($actor)) {
$condition = ['nurl' => normalise_link($actor), 'rel' => [Contact::SHARING, Contact::FRIEND],
'network' => Protocol::ACTIVITYPUB, 'archive' => false, 'pending' => false];
$contacts = DBA::select('contact', ['uid'], $condition);
while ($contact = DBA::fetch($contacts)) {
if ($contact['uid'] != 0) {
$receivers['uid:' . $contact['uid']] = $contact['uid'];
}
}
DBA::close($contacts);
continue;
}
$condition = ['self' => true, 'nurl' => normalise_link($receiver)];
$contact = DBA::selectFirst('contact', ['uid'], $condition);
if (!DBA::isResult($contact)) {
continue;
}
$receivers['uid:' . $contact['uid']] = $contact['uid'];
}
}
self::switchContacts($receivers, $actor);
return $receivers;
}
/**
* @brief
*
* @param $cid
* @param integer $uid User ID
* @param $url
*/
private static function switchContact($cid, $uid, $url)
{
$profile = ActivityPub::probeProfile($url);
if (empty($profile)) {
return;
}
logger('Switch contact ' . $cid . ' (' . $profile['url'] . ') for user ' . $uid . ' from OStatus to ActivityPub');
$photo = $profile['photo'];
unset($profile['photo']);
unset($profile['baseurl']);
$profile['nurl'] = normalise_link($profile['url']);
DBA::update('contact', $profile, ['id' => $cid]);
Contact::updateAvatar($photo, $uid, $cid);
}
/**
* @brief
*
* @param $receivers
* @param $actor
*/
private static function switchContacts($receivers, $actor)
{
if (empty($actor)) {
return;
}
foreach ($receivers as $receiver) {
$contact = DBA::selectFirst('contact', ['id'], ['uid' => $receiver, 'network' => Protocol::OSTATUS, 'nurl' => normalise_link($actor)]);
if (DBA::isResult($contact)) {
self::switchContact($contact['id'], $receiver, $actor);
}
$contact = DBA::selectFirst('contact', ['id'], ['uid' => $receiver, 'network' => Protocol::OSTATUS, 'alias' => [normalise_link($actor), $actor]]);
if (DBA::isResult($contact)) {
self::switchContact($contact['id'], $receiver, $actor);
}
}
}
/**
* @brief
*
* @param $object_data
* @param array $activity
*
* @return
*/
private static function addActivityFields($object_data, $activity)
{
if (!empty($activity['published']) && empty($object_data['published'])) {
$object_data['published'] = $activity['published'];
}
if (!empty($activity['updated']) && empty($object_data['updated'])) {
$object_data['updated'] = $activity['updated'];
}
if (!empty($activity['inReplyTo']) && empty($object_data['parent-uri'])) {
$object_data['parent-uri'] = JsonLD::fetchElement($activity, 'inReplyTo', 'id');
}
if (!empty($activity['instrument'])) {
$object_data['service'] = JsonLD::fetchElement($activity, 'instrument', 'name', 'type', 'Service');
}
return $object_data;
}
/**
* @brief
*
* @param $object_id
* @param $object
* @param $trust_source
*
* @return
*/
private static function fetchObject($object_id, $object = [], $trust_source = false)
{
if (!$trust_source || is_string($object)) {
$data = self::fetchContent($object_id);
if (empty($data)) {
logger('Empty content for ' . $object_id . ', check if content is available locally.', LOGGER_DEBUG);
$data = $object_id;
} else {
logger('Fetched content for ' . $object_id, LOGGER_DEBUG);
}
} else {
logger('Using original object for url ' . $object_id, LOGGER_DEBUG);
$data = $object;
}
if (is_string($data)) {
$item = Item::selectFirst([], ['uri' => $data]);
if (!DBA::isResult($item)) {
logger('Object with url ' . $data . ' was not found locally.', LOGGER_DEBUG);
return false;
}
logger('Using already stored item for url ' . $object_id, LOGGER_DEBUG);
$data = self::createNote($item);
}
if (empty($data['type'])) {
logger('Empty type', LOGGER_DEBUG);
return false;
}
if (in_array($data['type'], self::CONTENT_TYPES)) {
return self::processObject($data);
}
if ($data['type'] == 'Announce') {
if (empty($data['object'])) {
return false;
}
return self::fetchObject($data['object']);
}
logger('Unhandled object type: ' . $data['type'], LOGGER_DEBUG);
}
/**
* @brief
*
* @param $object
*
* @return
*/
private static function processObject($object)
{
if (empty($object['id'])) {
return false;
}
$object_data = [];
$object_data['object_type'] = $object['type'];
$object_data['id'] = $object['id'];
if (!empty($object['inReplyTo'])) {
$object_data['reply-to-id'] = JsonLD::fetchElement($object, 'inReplyTo', 'id');
} else {
$object_data['reply-to-id'] = $object_data['id'];
}
$object_data['published'] = defaults($object, 'published', null);
$object_data['updated'] = defaults($object, 'updated', $object_data['published']);
if (empty($object_data['published']) && !empty($object_data['updated'])) {
$object_data['published'] = $object_data['updated'];
}
$actor = JsonLD::fetchElement($object, 'attributedTo', 'id');
if (empty($actor)) {
$actor = defaults($object, 'actor', null);
}
$object_data['diaspora:guid'] = defaults($object, 'diaspora:guid', null);
$object_data['owner'] = $object_data['author'] = $actor;
$object_data['context'] = defaults($object, 'context', null);
$object_data['conversation'] = defaults($object, 'conversation', null);
$object_data['sensitive'] = defaults($object, 'sensitive', null);
$object_data['name'] = defaults($object, 'title', null);
$object_data['name'] = defaults($object, 'name', $object_data['name']);
$object_data['summary'] = defaults($object, 'summary', null);
$object_data['content'] = defaults($object, 'content', null);
$object_data['source'] = defaults($object, 'source', null);
$object_data['location'] = JsonLD::fetchElement($object, 'location', 'name', 'type', 'Place');
$object_data['attachments'] = defaults($object, 'attachment', null);
$object_data['tags'] = defaults($object, 'tag', null);
$object_data['service'] = JsonLD::fetchElement($object, 'instrument', 'name', 'type', 'Service');
$object_data['alternate-url'] = JsonLD::fetchElement($object, 'url', 'href');
$object_data['receiver'] = self::getReceivers($object, $object_data['owner']);
// Common object data:
// Unhandled
// @context, type, actor, signature, mediaType, duration, replies, icon
// Also missing: (Defined in the standard, but currently unused)
// audience, preview, endTime, startTime, generator, image
// Data in Notes:
// Unhandled
// contentMap, announcement_count, announcements, context_id, likes, like_count
// inReplyToStatusId, shares, quoteUrl, statusnetConversationId
// Data in video:
// To-Do?
// category, licence, language, commentsEnabled
// Unhandled
// views, waitTranscoding, state, support, subtitleLanguage
// likes, dislikes, shares, comments
return $object_data;
}
/**
* @brief Converts mentions from Pleroma into the Friendica format
*
* @param string $body
*
* @return converted body
*/
private static function convertMentions($body)
{
$URLSearchString = "^\[\]";
$body = preg_replace("/\[url\=([$URLSearchString]*)\]([#@!])(.*?)\[\/url\]/ism", '$2[url=$1]$3[/url]', $body);
return $body;
}
/**
* @brief Constructs a string with tags for a given tag array
*
* @param array $tags
* @param boolean $sensitive
*
* @return string with tags
*/
private static function constructTagList($tags, $sensitive)
{
if (empty($tags)) {
return '';
}
$tag_text = '';
foreach ($tags as $tag) {
if (in_array($tag['type'], ['Mention', 'Hashtag'])) {
if (!empty($tag_text)) {
$tag_text .= ',';
}
$tag_text .= substr($tag['name'], 0, 1) . '[url=' . $tag['href'] . ']' . substr($tag['name'], 1) . '[/url]';
}
}
/// @todo add nsfw for $sensitive
return $tag_text;
}
/**
* @brief
*
* @param $attachments
* @param array $item
*
* @return item array
*/
private static function constructAttachList($attachments, $item)
{
if (empty($attachments)) {
return $item;
}
foreach ($attachments as $attach) {
$filetype = strtolower(substr($attach['mediaType'], 0, strpos($attach['mediaType'], '/')));
if ($filetype == 'image') {
$item['body'] .= "\n[img]".$attach['url'].'[/img]';
} else {
if (!empty($item["attach"])) {
$item["attach"] .= ',';
} else {
$item["attach"] = '';
}
if (!isset($attach['length'])) {
$attach['length'] = "0";
}
$item["attach"] .= '[attach]href="'.$attach['url'].'" length="'.$attach['length'].'" type="'.$attach['mediaType'].'" title="'.defaults($attach, 'name', '').'"[/attach]';
}
}
return $item;
}
/**
* @brief
*
* @param array $activity
* @param $body
*/
private static function createItem($activity, $body)
{
$item = [];
$item['verb'] = ACTIVITY_POST;
$item['parent-uri'] = $activity['reply-to-id'];
if ($activity['reply-to-id'] == $activity['id']) {
$item['gravity'] = GRAVITY_PARENT;
$item['object-type'] = ACTIVITY_OBJ_NOTE;
} else {
$item['gravity'] = GRAVITY_COMMENT;
$item['object-type'] = ACTIVITY_OBJ_COMMENT;
}
if (($activity['id'] != $activity['reply-to-id']) && !Item::exists(['uri' => $activity['reply-to-id']])) {
logger('Parent ' . $activity['reply-to-id'] . ' not found. Try to refetch it.');
self::fetchMissingActivity($activity['reply-to-id'], $activity);
}
self::postItem($activity, $item, $body);
}
/**
* @brief
*
* @param array $activity
* @param $body
*/
private static function likeItem($activity, $body)
{
$item = [];
$item['verb'] = ACTIVITY_LIKE;
$item['parent-uri'] = $activity['object'];
$item['gravity'] = GRAVITY_ACTIVITY;
$item['object-type'] = ACTIVITY_OBJ_NOTE;
self::postItem($activity, $item, $body);
}
/**
* @brief Delete items
*
* @param array $activity
* @param $body
*/
private static function deleteItem($activity)
{
$owner = Contact::getIdForURL($activity['owner']);
$object = JsonLD::fetchElement($activity, 'object', 'id');
logger('Deleting item ' . $object . ' from ' . $owner, LOGGER_DEBUG);
Item::delete(['uri' => $object, 'owner-id' => $owner]);
}
/**
* @brief
*
* @param array $activity
* @param $body
*/
private static function dislikeItem($activity, $body)
{
$item = [];
$item['verb'] = ACTIVITY_DISLIKE;
$item['parent-uri'] = $activity['object'];
$item['gravity'] = GRAVITY_ACTIVITY;
$item['object-type'] = ACTIVITY_OBJ_NOTE;
self::postItem($activity, $item, $body);
}
/**
* @brief
*
* @param array $activity
* @param array $item
* @param $body
*/
private static function postItem($activity, $item, $body)
{
/// @todo What to do with $activity['context']?
if (($item['gravity'] != GRAVITY_PARENT) && !Item::exists(['uri' => $item['parent-uri']])) {
logger('Parent ' . $item['parent-uri'] . ' not found, message will be discarded.', LOGGER_DEBUG);
return;
}
$item['network'] = Protocol::ACTIVITYPUB;
$item['private'] = !in_array(0, $activity['receiver']);
$item['author-id'] = Contact::getIdForURL($activity['author'], 0, true);
$item['owner-id'] = Contact::getIdForURL($activity['owner'], 0, true);
$item['uri'] = $activity['id'];
$item['created'] = $activity['published'];
$item['edited'] = $activity['updated'];
$item['guid'] = $activity['diaspora:guid'];
$item['title'] = HTML::toBBCode($activity['name']);
$item['content-warning'] = HTML::toBBCode($activity['summary']);
$item['body'] = self::convertMentions(HTML::toBBCode($activity['content']));
$item['location'] = $activity['location'];
$item['tag'] = self::constructTagList($activity['tags'], $activity['sensitive']);
$item['app'] = $activity['service'];
$item['plink'] = defaults($activity, 'alternate-url', $item['uri']);
$item = self::constructAttachList($activity['attachments'], $item);
$source = JsonLD::fetchElement($activity, 'source', 'content', 'mediaType', 'text/bbcode');
if (!empty($source)) {
$item['body'] = $source;
}
$item['protocol'] = Conversation::PARCEL_ACTIVITYPUB;
$item['source'] = $body;
$item['conversation-href'] = $activity['context'];
$item['conversation-uri'] = $activity['conversation'];
foreach ($activity['receiver'] as $receiver) {
$item['uid'] = $receiver;
$item['contact-id'] = Contact::getIdForURL($activity['author'], $receiver, true);
if (($receiver != 0) && empty($item['contact-id'])) {
$item['contact-id'] = Contact::getIdForURL($activity['author'], 0, true);
}
$item_id = Item::insert($item);
logger('Storing for user ' . $item['uid'] . ': ' . $item_id);
}
}
/**
* @brief
*
* @param $url
* @param $child
*/
private static function fetchMissingActivity($url, $child)
{
if (Config::get('system', 'ostatus_full_threads')) {
return;
}
$object = ActivityPub::fetchContent($url);
if (empty($object)) {
logger('Activity ' . $url . ' was not fetchable, aborting.');
return;
}
$activity = [];
$activity['@context'] = $object['@context'];
unset($object['@context']);
$activity['id'] = $object['id'];
$activity['to'] = defaults($object, 'to', []);
$activity['cc'] = defaults($object, 'cc', []);
$activity['actor'] = $child['author'];
$activity['object'] = $object;
$activity['published'] = $object['published'];
$activity['type'] = 'Create';
self::processActivity($activity);
logger('Activity ' . $url . ' had been fetched and processed.');
}
/**
* @brief perform a "follow" request
*
* @param array $activity
*/
private static function followUser($activity)
{
$actor = JsonLD::fetchElement($activity, 'object', 'id');
$uid = User::getIdForURL($actor);
if (empty($uid)) {
return;
}
$owner = User::getOwnerDataById($uid);
$cid = Contact::getIdForURL($activity['owner'], $uid);
if (!empty($cid)) {
$contact = DBA::selectFirst('contact', [], ['id' => $cid]);
} else {
$contact = false;
}
$item = ['author-id' => Contact::getIdForURL($activity['owner']),
'author-link' => $activity['owner']];
Contact::addRelationship($owner, $contact, $item);
$cid = Contact::getIdForURL($activity['owner'], $uid);
if (empty($cid)) {
return;
}
$contact = DBA::selectFirst('contact', ['network'], ['id' => $cid]);
if ($contact['network'] != Protocol::ACTIVITYPUB) {
Contact::updateFromProbe($cid, Protocol::ACTIVITYPUB);
}
DBA::update('contact', ['hub-verify' => $activity['id']], ['id' => $cid]);
logger('Follow user ' . $uid . ' from contact ' . $cid . ' with id ' . $activity['id']);
}
/**
* @brief Update the given profile
*
* @param array $activity
*/
private static function updatePerson($activity)
{
if (empty($activity['object']['id'])) {
return;
}
logger('Updating profile for ' . $activity['object']['id'], LOGGER_DEBUG);
APContact::getByURL($activity['object']['id'], true);
}
/**
* @brief Delete the given profile
*
* @param array $activity
*/
private static function deletePerson($activity)
{
if (empty($activity['object']['id']) || empty($activity['object']['actor'])) {
logger('Empty object id or actor.', LOGGER_DEBUG);
return;
}
if ($activity['object']['id'] != $activity['object']['actor']) {
logger('Object id does not match actor.', LOGGER_DEBUG);
return;
}
$contacts = DBA::select('contact', ['id'], ['nurl' => normalise_link($activity['object']['id'])]);
while ($contact = DBA::fetch($contacts)) {
Contact::remove($contact["id"]);
}
DBA::close($contacts);
logger('Deleted contact ' . $activity['object']['id'], LOGGER_DEBUG);
}
/**
* @brief Accept a follow request
*
* @param array $activity
*/
private static function acceptFollowUser($activity)
{
$actor = JsonLD::fetchElement($activity, 'object', 'actor');
$uid = User::getIdForURL($actor);
if (empty($uid)) {
return;
}
$owner = User::getOwnerDataById($uid);
$cid = Contact::getIdForURL($activity['owner'], $uid);
if (empty($cid)) {
logger('No contact found for ' . $activity['owner'], LOGGER_DEBUG);
return;
}
$fields = ['pending' => false];
$contact = DBA::selectFirst('contact', ['rel'], ['id' => $cid]);
if ($contact['rel'] == Contact::FOLLOWER) {
$fields['rel'] = Contact::FRIEND;
}
$condition = ['id' => $cid];
DBA::update('contact', $fields, $condition);
logger('Accept contact request from contact ' . $cid . ' for user ' . $uid, LOGGER_DEBUG);
}
/**
* @brief Reject a follow request
*
* @param array $activity
*/
private static function rejectFollowUser($activity)
{
$actor = JsonLD::fetchElement($activity, 'object', 'actor');
$uid = User::getIdForURL($actor);
if (empty($uid)) {
return;
}
$owner = User::getOwnerDataById($uid);
$cid = Contact::getIdForURL($activity['owner'], $uid);
if (empty($cid)) {
logger('No contact found for ' . $activity['owner'], LOGGER_DEBUG);
return;
}
if (DBA::exists('contact', ['id' => $cid, 'rel' => Contact::SHARING, 'pending' => true])) {
Contact::remove($cid);
logger('Rejected contact request from contact ' . $cid . ' for user ' . $uid . ' - contact had been removed.', LOGGER_DEBUG);
} else {
logger('Rejected contact request from contact ' . $cid . ' for user ' . $uid . '.', LOGGER_DEBUG);
}
}
/**
* @brief Undo activity like "like" or "dislike"
*
* @param array $activity
*/
private static function undoActivity($activity)
{
$activity_url = JsonLD::fetchElement($activity, 'object', 'id');
if (empty($activity_url)) {
return;
}
$actor = JsonLD::fetchElement($activity, 'object', 'actor');
if (empty($actor)) {
return;
}
$author_id = Contact::getIdForURL($actor);
if (empty($author_id)) {
return;
}
Item::delete(['uri' => $activity_url, 'author-id' => $author_id, 'gravity' => GRAVITY_ACTIVITY]);
}
/**
* @brief Activity to remove a follower
*
* @param array $activity
*/
private static function undoFollowUser($activity)
{
$object = JsonLD::fetchElement($activity, 'object', 'object');
$uid = User::getIdForURL($object);
if (empty($uid)) {
return;
}
$owner = User::getOwnerDataById($uid);
$cid = Contact::getIdForURL($activity['owner'], $uid);
if (empty($cid)) {
logger('No contact found for ' . $activity['owner'], LOGGER_DEBUG);
return;
}
$contact = DBA::selectFirst('contact', [], ['id' => $cid]);
if (!DBA::isResult($contact)) {
return;
}
Contact::removeFollower($owner, $contact);
logger('Undo following request from contact ' . $cid . ' for user ' . $uid, LOGGER_DEBUG);
}
}

View file

@ -1592,17 +1592,13 @@ class Diaspora
if (DBA::isResult($item)) { if (DBA::isResult($item)) {
return $item["uri"]; return $item["uri"];
} elseif (!$onlyfound) { } elseif (!$onlyfound) {
$contact = Contact::getDetailsByAddr($author, 0); $person = self::personByHandle($author);
if (!empty($contact['network'])) {
$prefix = 'urn:X-' . $contact['network'] . ':';
} else {
// This fallback should happen most unlikely
$prefix = 'urn:X-dspr:';
}
$author_parts = explode('@', $author); $parts = parse_url($person['url']);
unset($parts['path']);
$host_url = Network::unparseURL($parts);
return $prefix . $author_parts[1] . ':' . $author_parts[0] . ':'. $guid; return $host_url . '/object/' . $guid;
} }
return ""; return "";
@ -3204,7 +3200,7 @@ class Diaspora
$author = self::myHandle($owner); $author = self::myHandle($owner);
$message = ["author" => $author, $message = ["author" => $author,
"guid" => System::createGUID(32), "guid" => System::createUUID(),
"parent_type" => "Post", "parent_type" => "Post",
"parent_guid" => $item["guid"]]; "parent_guid" => $item["guid"]];

View file

@ -2004,8 +2004,7 @@ class OStatus
} }
if (intval($item["parent"]) > 0) { if (intval($item["parent"]) > 0) {
$conversation_href = System::baseUrl()."/display/".$owner["nick"]."/".$item["parent"]; $conversation_href = $conversation_uri = str_replace('/object/', '/context/', $item['parent-uri']);
$conversation_uri = $conversation_href;
if (isset($parent_item)) { if (isset($parent_item)) {
$conversation = DBA::selectFirst('conversation', ['conversation-uri', 'conversation-href'], ['item-uri' => $parent_item]); $conversation = DBA::selectFirst('conversation', ['conversation-uri', 'conversation-href'], ['item-uri' => $parent_item]);

View file

@ -333,7 +333,7 @@ class PortableContact
$server_url = normalise_link(self::detectServer($profile)); $server_url = normalise_link(self::detectServer($profile));
} }
if (!in_array($gcontacts[0]["network"], [Protocol::DFRN, Protocol::DIASPORA, Protocol::FEED, Protocol::OSTATUS, ""])) { if (!in_array($gcontacts[0]["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::FEED, Protocol::OSTATUS, ""])) {
logger("Profile ".$profile.": Network type ".$gcontacts[0]["network"]." can't be checked", LOGGER_DEBUG); logger("Profile ".$profile.": Network type ".$gcontacts[0]["network"]." can't be checked", LOGGER_DEBUG);
return false; return false;
} }

View file

@ -5,67 +5,45 @@
*/ */
namespace Friendica\Util; namespace Friendica\Util;
use Friendica\BaseObject;
use Friendica\Core\Config; use Friendica\Core\Config;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\Model\User;
use Friendica\Model\APContact;
use Friendica\Protocol\ActivityPub;
/** /**
* @brief Implements HTTP Signatures per draft-cavage-http-signatures-07. * @brief Implements HTTP Signatures per draft-cavage-http-signatures-07.
* *
* Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/Zotlabs/Web/HTTPSig.php * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/Zotlabs/Web/HTTPSig.php
* *
* Other parts of the code for HTTP signing are taken from the Osada project.
* https://framagit.org/macgirvin/osada
*
* @see https://tools.ietf.org/html/draft-cavage-http-signatures-07 * @see https://tools.ietf.org/html/draft-cavage-http-signatures-07
*/ */
class HTTPSignature class HTTPSignature
{ {
/**
* @brief RFC5843
*
* Disabled until Friendica's ActivityPub implementation
* is ready.
*
* @see https://tools.ietf.org/html/rfc5843
*
* @param string $body The value to create the digest for
* @param boolean $set (optional, default true)
* If set send a Digest HTTP header
*
* @return string The generated digest of $body
*/
// public static function generateDigest($body, $set = true)
// {
// $digest = base64_encode(hash('sha256', $body, true));
//
// if($set) {
// header('Digest: SHA-256=' . $digest);
// }
// return $digest;
// }
// See draft-cavage-http-signatures-08 // See draft-cavage-http-signatures-08
public static function verify($data, $key = '') /**
* @brief Verifies a magic request
*
* @param $key
*
* @return array with verification data
*/
public static function verifyMagic($key)
{ {
$body = $data;
$headers = null; $headers = null;
$spoofable = false; $spoofable = false;
$result = [ $result = [
'signer' => '', 'signer' => '',
'header_signed' => false, 'header_signed' => false,
'header_valid' => false, 'header_valid' => false
'content_signed' => false,
'content_valid' => false
]; ];
// Decide if $data arrived via controller submission or curl. // Decide if $data arrived via controller submission or curl.
if (is_array($data) && $data['header']) {
if (!$data['success']) {
return $result;
}
$h = new HTTPHeaders($data['header']);
$headers = $h->fetch();
$body = $data['body'];
} else {
$headers = []; $headers = [];
$headers['(request-target)'] = strtolower($_SERVER['REQUEST_METHOD']).' '.$_SERVER['REQUEST_URI']; $headers['(request-target)'] = strtolower($_SERVER['REQUEST_METHOD']).' '.$_SERVER['REQUEST_URI'];
@ -75,24 +53,16 @@ class HTTPSignature
$headers[$field] = $v; $headers[$field] = $v;
} }
} }
}
$sig_block = null; $sig_block = null;
if (array_key_exists('signature', $headers)) {
$sig_block = self::parseSigheader($headers['signature']);
} elseif (array_key_exists('authorization', $headers)) {
$sig_block = self::parseSigheader($headers['authorization']); $sig_block = self::parseSigheader($headers['authorization']);
}
if (!$sig_block) { if (!$sig_block) {
logger('no signature provided.'); logger('no signature provided.');
return $result; return $result;
} }
// Warning: This log statement includes binary data
// logger('sig_block: ' . print_r($sig_block,true), LOGGER_DATA);
$result['header_signed'] = true; $result['header_signed'] = true;
$signed_headers = $sig_block['headers']; $signed_headers = $sig_block['headers'];
@ -112,13 +82,7 @@ class HTTPSignature
$signed_data = rtrim($signed_data, "\n"); $signed_data = rtrim($signed_data, "\n");
$algorithm = null;
if ($sig_block['algorithm'] === 'rsa-sha256') {
$algorithm = 'sha256';
}
if ($sig_block['algorithm'] === 'rsa-sha512') {
$algorithm = 'sha512'; $algorithm = 'sha512';
}
if ($key && function_exists($key)) { if ($key && function_exists($key)) {
$result['signer'] = $sig_block['keyId']; $result['signer'] = $sig_block['keyId'];
@ -127,12 +91,6 @@ class HTTPSignature
logger('Got keyID ' . $sig_block['keyId']); logger('Got keyID ' . $sig_block['keyId']);
// We don't use Activity Pub at the moment.
// if (!$key) {
// $result['signer'] = $sig_block['keyId'];
// $key = self::getActivitypubKey($sig_block['keyId']);
// }
if (!$key) { if (!$key) {
return $result; return $result;
} }
@ -149,130 +107,39 @@ class HTTPSignature
$result['header_valid'] = true; $result['header_valid'] = true;
} }
if (in_array('digest', $signed_headers)) {
$result['content_signed'] = true;
$digest = explode('=', $headers['digest']);
if ($digest[0] === 'SHA-256') {
$hashalg = 'sha256';
}
if ($digest[0] === 'SHA-512') {
$hashalg = 'sha512';
}
// The explode operation will have stripped the '=' padding, so compare against unpadded base64.
if (rtrim(base64_encode(hash($hashalg, $body, true)), '=') === $digest[1]) {
$result['content_valid'] = true;
}
}
logger('Content_Valid: ' . $result['content_valid']);
return $result; return $result;
} }
/**
* Fetch the public key for Activity Pub contact.
*
* @param string|int The identifier (contact addr or contact ID).
* @return string|boolean The public key or false on failure.
*/
private static function getActivitypubKey($id)
{
if (strpos($id, 'acct:') === 0) {
$contact = DBA::selectFirst('contact', ['pubkey'], ['uid' => 0, 'addr' => str_replace('acct:', '', $id)]);
} else {
$contact = DBA::selectFirst('contact', ['pubkey'], ['id' => $id, 'network' => 'activitypub']);
}
if (DBA::isResult($contact)) {
return $contact['pubkey'];
}
if(function_exists('as_fetch')) {
$r = as_fetch($id);
}
if ($r) {
$j = json_decode($r, true);
if (array_key_exists('publicKey', $j) && array_key_exists('publicKeyPem', $j['publicKey'])) {
if ((array_key_exists('id', $j['publicKey']) && $j['publicKey']['id'] !== $id) && $j['id'] !== $id) {
return false;
}
return $j['publicKey']['publicKeyPem'];
}
}
return false;
}
/** /**
* @brief * @brief
* *
* @param string $request
* @param array $head * @param array $head
* @param string $prvkey * @param string $prvkey
* @param string $keyid (optional, default 'Key') * @param string $keyid (optional, default 'Key')
* @param boolean $send_headers (optional, default false)
* If set send a HTTP header
* @param boolean $auth (optional, default false)
* @param string $alg (optional, default 'sha256')
* @param string $crypt_key (optional, default null)
* @param string $crypt_algo (optional, default 'aes256ctr')
* *
* @return array * @return array
*/ */
public static function createSig($request, $head, $prvkey, $keyid = 'Key', $send_headers = false, $auth = false, $alg = 'sha256', $crypt_key = null, $crypt_algo = 'aes256ctr') public static function createSig($head, $prvkey, $keyid = 'Key')
{ {
$return_headers = []; $return_headers = [];
if ($alg === 'sha256') { $alg = 'sha512';
$algorithm = 'rsa-sha256';
}
if ($alg === 'sha512') {
$algorithm = 'rsa-sha512'; $algorithm = 'rsa-sha512';
}
$x = self::sign($request, $head, $prvkey, $alg); $x = self::sign($head, $prvkey, $alg);
$headerval = 'keyId="' . $keyid . '",algorithm="' . $algorithm $headerval = 'keyId="' . $keyid . '",algorithm="' . $algorithm
. '",headers="' . $x['headers'] . '",signature="' . $x['signature'] . '"'; . '",headers="' . $x['headers'] . '",signature="' . $x['signature'] . '"';
if ($crypt_key) {
$x = Crypto::encapsulate($headerval, $crypt_key, $crypt_algo);
$headerval = 'iv="' . $x['iv'] . '",key="' . $x['key'] . '",alg="' . $x['alg'] . '",data="' . $x['data'] . '"';
}
if ($auth) {
$sighead = 'Authorization: Signature ' . $headerval; $sighead = 'Authorization: Signature ' . $headerval;
} else {
$sighead = 'Signature: ' . $headerval;
}
if ($head) { if ($head) {
foreach ($head as $k => $v) { foreach ($head as $k => $v) {
if ($send_headers) {
// This is for ActivityPub implementation.
// Since the Activity Pub implementation isn't
// ready at the moment, we comment it out.
// header($k . ': ' . $v);
} else {
$return_headers[] = $k . ': ' . $v; $return_headers[] = $k . ': ' . $v;
} }
} }
}
if ($send_headers) {
// This is for ActivityPub implementation.
// Since the Activity Pub implementation isn't
// ready at the moment, we comment it out.
// header($sighead);
} else {
$return_headers[] = $sighead; $return_headers[] = $sighead;
}
return $return_headers; return $return_headers;
} }
@ -280,25 +147,18 @@ class HTTPSignature
/** /**
* @brief * @brief
* *
* @param string $request
* @param array $head * @param array $head
* @param string $prvkey * @param string $prvkey
* @param string $alg (optional) default 'sha256' * @param string $alg (optional) default 'sha256'
* *
* @return array * @return array
*/ */
private static function sign($request, $head, $prvkey, $alg = 'sha256') private static function sign($head, $prvkey, $alg = 'sha256')
{ {
$ret = []; $ret = [];
$headers = ''; $headers = '';
$fields = ''; $fields = '';
if ($request) {
$headers = '(request-target)' . ': ' . trim($request) . "\n";
$fields = '(request-target)';
}
if ($head) {
foreach ($head as $k => $v) { foreach ($head as $k => $v) {
$headers .= strtolower($k) . ': ' . trim($v) . "\n"; $headers .= strtolower($k) . ': ' . trim($v) . "\n";
if ($fields) { if ($fields) {
@ -308,7 +168,6 @@ class HTTPSignature
} }
// strip the trailing linefeed // strip the trailing linefeed
$headers = rtrim($headers, "\n"); $headers = rtrim($headers, "\n");
}
$sig = base64_encode(Crypto::rsaSign($headers, $prvkey, $alg)); $sig = base64_encode(Crypto::rsaSign($headers, $prvkey, $alg));
@ -405,4 +264,178 @@ class HTTPSignature
return ''; return '';
} }
/*
* Functions for ActivityPub
*/
/**
* @brief Transmit given data to a target for a user
*
* @param $data
* @param $target
* @param $uid
*/
public static function transmit($data, $target, $uid)
{
$owner = User::getOwnerDataById($uid);
if (!$owner) {
return;
}
$content = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
// Header data that is about to be signed.
$host = parse_url($target, PHP_URL_HOST);
$path = parse_url($target, PHP_URL_PATH);
$digest = 'SHA-256=' . base64_encode(hash('sha256', $content, true));
$content_length = strlen($content);
$headers = ['Content-Length: ' . $content_length, 'Digest: ' . $digest, 'Host: ' . $host];
$signed_data = "(request-target): post " . $path . "\ncontent-length: " . $content_length . "\ndigest: " . $digest . "\nhost: " . $host;
$signature = base64_encode(Crypto::rsaSign($signed_data, $owner['uprvkey'], 'sha256'));
$headers[] = 'Signature: keyId="' . $owner['url'] . '#main-key' . '",algorithm="rsa-sha256",headers="(request-target) content-length digest host",signature="' . $signature . '"';
$headers[] = 'Content-Type: application/activity+json';
Network::post($target, $content, $headers);
$return_code = BaseObject::getApp()->get_curl_code();
logger('Transmit to ' . $target . ' returned ' . $return_code);
}
/**
* @brief Gets a signer from a given HTTP request
*
* @param $content
* @param $http_headers
*
* @return signer string
*/
public static function getSigner($content, $http_headers)
{
$object = json_decode($content, true);
if (empty($object)) {
return false;
}
$actor = JsonLD::fetchElement($object, 'actor', 'id');
$headers = [];
$headers['(request-target)'] = strtolower($http_headers['REQUEST_METHOD']) . ' ' . $http_headers['REQUEST_URI'];
// First take every header
foreach ($http_headers as $k => $v) {
$field = str_replace('_', '-', strtolower($k));
$headers[$field] = $v;
}
// Now add every http header
foreach ($http_headers as $k => $v) {
if (strpos($k, 'HTTP_') === 0) {
$field = str_replace('_', '-', strtolower(substr($k, 5)));
$headers[$field] = $v;
}
}
$sig_block = self::parseSigHeader($http_headers['HTTP_SIGNATURE']);
if (empty($sig_block) || empty($sig_block['headers']) || empty($sig_block['keyId'])) {
return false;
}
$signed_data = '';
foreach ($sig_block['headers'] as $h) {
if (array_key_exists($h, $headers)) {
$signed_data .= $h . ': ' . $headers[$h] . "\n";
}
}
$signed_data = rtrim($signed_data, "\n");
if (empty($signed_data)) {
return false;
}
$algorithm = null;
if ($sig_block['algorithm'] === 'rsa-sha256') {
$algorithm = 'sha256';
}
if ($sig_block['algorithm'] === 'rsa-sha512') {
$algorithm = 'sha512';
}
if (empty($algorithm)) {
return false;
}
$key = self::fetchKey($sig_block['keyId'], $actor);
if (empty($key)) {
return false;
}
if (!Crypto::rsaVerify($signed_data, $sig_block['signature'], $key['pubkey'], $algorithm)) {
return false;
}
// Check the digest when it is part of the signed data
if (in_array('digest', $sig_block['headers'])) {
$digest = explode('=', $headers['digest'], 2);
if ($digest[0] === 'SHA-256') {
$hashalg = 'sha256';
}
if ($digest[0] === 'SHA-512') {
$hashalg = 'sha512';
}
/// @todo add all hashes from the rfc
if (!empty($hashalg) && base64_encode(hash($hashalg, $content, true)) != $digest[1]) {
return false;
}
}
// Check the content-length when it is part of the signed data
if (in_array('content-length', $sig_block['headers'])) {
if (strlen($content) != $headers['content-length']) {
return false;
}
}
return $key['url'];
}
/**
* @brief fetches a key for a given id and actor
*
* @param $id
* @param $actor
*
* @return array with actor url and public key
*/
private static function fetchKey($id, $actor)
{
$url = (strpos($id, '#') ? substr($id, 0, strpos($id, '#')) : $id);
$profile = APContact::getByURL($url);
if (!empty($profile)) {
logger('Taking key from id ' . $id, LOGGER_DEBUG);
return ['url' => $url, 'pubkey' => $profile['pubkey']];
} elseif ($url != $actor) {
$profile = APContact::getByURL($actor);
if (!empty($profile)) {
logger('Taking key from actor ' . $actor, LOGGER_DEBUG);
return ['url' => $actor, 'pubkey' => $profile['pubkey']];
}
}
return false;
}
} }

143
src/Util/JsonLD.php Normal file
View file

@ -0,0 +1,143 @@
<?php
/**
* @file src/Util/JsonLD.php
*/
namespace Friendica\Util;
use Friendica\Core\Cache;
use Exception;
/**
* @brief This class contain methods to work with JsonLD data
*/
class JsonLD
{
/**
* @brief Loader for LD-JSON validation
*
* @param $url
*
* @return the loaded data
*/
public static function documentLoader($url)
{
$recursion = 0;
$x = debug_backtrace();
if ($x) {
foreach ($x as $n) {
if ($n['function'] === __FUNCTION__) {
$recursion ++;
}
}
}
if ($recursion > 5) {
logger('jsonld bomb detected at: ' . $url);
exit();
}
$result = Cache::get('documentLoader:' . $url);
if (!is_null($result)) {
return $result;
}
$data = jsonld_default_document_loader($url);
Cache::set('documentLoader:' . $url, $data, CACHE_DAY);
return $data;
}
/**
* @brief Normalises a given JSON array
*
* @param array $json
*
* @return normalized JSON string
*/
public static function normalize($json)
{
jsonld_set_document_loader('Friendica\Util\JsonLD::documentLoader');
$jsonobj = json_decode(json_encode($json, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
try {
$normalized = jsonld_normalize($jsonobj, array('algorithm' => 'URDNA2015', 'format' => 'application/nquads'));
}
catch (Exception $e) {
$normalized = false;
logger('normalise error:' . print_r($e, true), LOGGER_DEBUG);
}
return $normalized;
}
/**
* @brief Compacts a given JSON array
*
* @param array $json
*
* @return comacted JSON array
*/
public static function compact($json)
{
jsonld_set_document_loader('Friendica\Util\JsonLD::documentLoader');
$context = (object)['as' => 'https://www.w3.org/ns/activitystreams',
'w3sec' => 'https://w3id.org/security',
'ostatus' => (object)['@id' => 'http://ostatus.org#', '@type' => '@id'],
'vcard' => (object)['@id' => 'http://www.w3.org/2006/vcard/ns#', '@type' => '@id'],
'uuid' => (object)['@id' => 'http://schema.org/identifier', '@type' => '@id']];
$jsonobj = json_decode(json_encode($json, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
$compacted = jsonld_compact($jsonobj, $context);
return json_decode(json_encode($compacted, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), true);
}
/**
* @brief Fetches an element from a JSON array
*
* @param $array
* @param $element
* @param $key
* @param $type
* @param $type_value
*
* @return fetched element
*/
public static function fetchElement($array, $element, $key, $type = null, $type_value = null)
{
if (empty($array)) {
return false;
}
if (empty($array[$element])) {
return false;
}
if (is_string($array[$element])) {
return $array[$element];
}
if (is_null($type_value)) {
if (!empty($array[$element][$key])) {
return $array[$element][$key];
}
if (!empty($array[$element][0][$key])) {
return $array[$element][0][$key];
}
return false;
}
if (!empty($array[$element][$key]) && !empty($array[$element][$type]) && ($array[$element][$type] == $type_value)) {
return $array[$element][$key];
}
/// @todo Add array search
return false;
}
}

89
src/Util/LDSignature.php Normal file
View file

@ -0,0 +1,89 @@
<?php
namespace Friendica\Util;
use Friendica\Util\JsonLD;
use Friendica\Util\DateTimeFormat;
use Friendica\Protocol\ActivityPub;
use Friendica\Model\APContact;
/**
* @brief Implements JSON-LD signatures
*
* Ported from Osada: https://framagit.org/macgirvin/osada
*/
class LDSignature
{
public static function isSigned($data)
{
return !empty($data['signature']);
}
public static function getSigner($data)
{
if (!self::isSigned($data)) {
return false;
}
$actor = JsonLD::fetchElement($data, 'actor', 'id');
if (empty($actor)) {
return false;
}
$profile = APContact::getByURL($actor);
if (empty($profile['pubkey'])) {
return false;
}
$pubkey = $profile['pubkey'];
$ohash = self::hash(self::signableOptions($data['signature']));
$dhash = self::hash(self::signableData($data));
$x = Crypto::rsaVerify($ohash . $dhash, base64_decode($data['signature']['signatureValue']), $pubkey);
logger('LD-verify: ' . intval($x));
if (empty($x)) {
return false;
} else {
return $actor;
}
}
public static function sign($data, $owner)
{
$options = [
'type' => 'RsaSignature2017',
'nonce' => random_string(64),
'creator' => $owner['url'] . '#main-key',
'created' => DateTimeFormat::utcNow(DateTimeFormat::ATOM)
];
$ohash = self::hash(self::signableOptions($options));
$dhash = self::hash(self::signableData($data));
$options['signatureValue'] = base64_encode(Crypto::rsaSign($ohash . $dhash, $owner['uprvkey']));
return array_merge($data, ['signature' => $options]);
}
private static function signableData($data)
{
unset($data['signature']);
return $data;
}
private static function signableOptions($options)
{
$newopts = ['@context' => 'https://w3id.org/identity/v1'];
unset($options['type']);
unset($options['id']);
unset($options['signatureValue']);
return array_merge($newopts, $options);
}
private static function hash($obj)
{
return hash('sha256', JsonLD::normalize($obj));
}
}

34
src/Worker/APDelivery.php Normal file
View file

@ -0,0 +1,34 @@
<?php
/**
* @file src/Worker/APDelivery.php
*/
namespace Friendica\Worker;
use Friendica\BaseObject;
use Friendica\Protocol\ActivityPub;
use Friendica\Model\Item;
use Friendica\Util\HTTPSignature;
class APDelivery extends BaseObject
{
public static function execute($cmd, $item_id, $inbox, $uid)
{
logger('Invoked: ' . $cmd . ': ' . $item_id . ' to ' . $inbox, LOGGER_DEBUG);
if ($cmd == Delivery::MAIL) {
} elseif ($cmd == Delivery::SUGGESTION) {
} elseif ($cmd == Delivery::RELOCATION) {
} elseif ($cmd == Delivery::REMOVAL) {
ActivityPub::transmitProfileDeletion($uid, $inbox);
} elseif ($cmd == Delivery::PROFILEUPDATE) {
ActivityPub::transmitProfileUpdate($uid, $inbox);
} else {
$data = ActivityPub::createActivityFromItem($item_id);
if (!empty($data)) {
HTTPSignature::transmit($data, $inbox, $uid);
}
}
return;
}
}

View file

@ -29,6 +29,7 @@ class Delivery extends BaseObject
const POST = 'wall-new'; const POST = 'wall-new';
const COMMENT = 'comment-new'; const COMMENT = 'comment-new';
const REMOVAL = 'removeme'; const REMOVAL = 'removeme';
const PROFILEUPDATE = 'profileupdate';
public static function execute($cmd, $item_id, $contact_id) public static function execute($cmd, $item_id, $contact_id)
{ {

View file

@ -16,6 +16,7 @@ use Friendica\Model\Item;
use Friendica\Model\PushSubscriber; use Friendica\Model\PushSubscriber;
use Friendica\Model\User; use Friendica\Model\User;
use Friendica\Network\Probe; use Friendica\Network\Probe;
use Friendica\Protocol\ActivityPub;
use Friendica\Protocol\Diaspora; use Friendica\Protocol\Diaspora;
use Friendica\Protocol\OStatus; use Friendica\Protocol\OStatus;
use Friendica\Protocol\Salmon; use Friendica\Protocol\Salmon;
@ -98,6 +99,14 @@ class Notifier
foreach ($r as $contact) { foreach ($r as $contact) {
Contact::terminateFriendship($user, $contact, true); Contact::terminateFriendship($user, $contact, true);
} }
$inboxes = ActivityPub::fetchTargetInboxesforUser(0);
foreach ($inboxes as $inbox) {
logger('Account removal for user ' . $uid . ' to ' . $inbox .' via ActivityPub', LOGGER_DEBUG);
Worker::add(['priority' => $a->queue['priority'], 'created' => $a->queue['created'], 'dont_fork' => true],
'APDelivery', Delivery::REMOVAL, '', $inbox, $uid);
}
return; return;
} elseif ($cmd == Delivery::RELOCATION) { } elseif ($cmd == Delivery::RELOCATION) {
$normal_mode = false; $normal_mode = false;
@ -413,6 +422,24 @@ class Notifier
} }
} }
$inboxes = [];
if ($target_item['origin']) {
$inboxes = ActivityPub::fetchTargetInboxes($target_item, $uid);
}
if ($parent['origin']) {
$parent_inboxes = ActivityPub::fetchTargetInboxes($parent, $uid);
$inboxes = array_merge($inboxes, $parent_inboxes);
}
foreach ($inboxes as $inbox) {
logger('Deliver ' . $item_id .' to ' . $inbox .' via ActivityPub', LOGGER_DEBUG);
Worker::add(['priority' => $a->queue['priority'], 'created' => $a->queue['created'], 'dont_fork' => true],
'APDelivery', $cmd, $item_id, $inbox, $uid);
}
// send salmon slaps to mentioned remote tags (@foo@example.com) in OStatus posts // send salmon slaps to mentioned remote tags (@foo@example.com) in OStatus posts
// They are especially used for notifications to OStatus users that don't follow us. // They are especially used for notifications to OStatus users that don't follow us.
if (!Config::get('system', 'dfrn_only') && count($url_recipients) && ($public_message || $push_notify) && $normal_mode) { if (!Config::get('system', 'dfrn_only') && count($url_recipients) && ($public_message || $push_notify) && $normal_mode) {

View file

@ -1,12 +1,15 @@
<?php <?php
/** /**
* @file src/Worker/ProfileUpdate.php * @file src/Worker/ProfileUpdate.php
* @brief Send updated profile data to Diaspora * @brief Send updated profile data to Diaspora and ActivityPub
*/ */
namespace Friendica\Worker; namespace Friendica\Worker;
use Friendica\BaseObject;
use Friendica\Protocol\Diaspora; use Friendica\Protocol\Diaspora;
use Friendica\Protocol\ActivityPub;
use Friendica\Core\Worker;
class ProfileUpdate { class ProfileUpdate {
public static function execute($uid = 0) { public static function execute($uid = 0) {
@ -14,6 +17,16 @@ class ProfileUpdate {
return; return;
} }
$a = BaseObject::getApp();
$inboxes = ActivityPub::fetchTargetInboxesforUser($uid);
foreach ($inboxes as $inbox) {
logger('Profile update for user ' . $uid . ' to ' . $inbox .' via ActivityPub', LOGGER_DEBUG);
Worker::add(['priority' => $a->queue['priority'], 'created' => $a->queue['created'], 'dont_fork' => true],
'APDelivery', Delivery::PROFILEUPDATE, '', $inbox, $uid);
}
Diaspora::sendProfile($uid); Diaspora::sendProfile($uid);
} }
} }

View file

@ -29,13 +29,13 @@ class UpdateGContact
return; return;
} }
if (!in_array($r[0]["network"], [Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) { if (!in_array($r[0]["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) {
return; return;
} }
$data = Probe::uri($r[0]["url"]); $data = Probe::uri($r[0]["url"]);
if (!in_array($data["network"], [Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) { if (!in_array($data["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) {
if ($r[0]["server_url"] != "") { if ($r[0]["server_url"] != "") {
PortableContact::checkServer($r[0]["server_url"], $r[0]["network"]); PortableContact::checkServer($r[0]["server_url"], $r[0]["network"]);
} }

View file

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-08-25 15:34+0000\n" "POT-Creation-Date: 2018-09-27 21:18+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -18,47 +18,47 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: index.php:261 mod/apps.php:14 #: index.php:265 mod/apps.php:14
msgid "You must be logged in to use addons. " msgid "You must be logged in to use addons. "
msgstr "" msgstr ""
#: index.php:308 mod/fetch.php:20 mod/fetch.php:47 mod/fetch.php:54 #: index.php:312 mod/fetch.php:20 mod/fetch.php:47 mod/fetch.php:54
#: mod/help.php:62 #: mod/help.php:62
msgid "Not Found" msgid "Not Found"
msgstr "" msgstr ""
#: index.php:313 mod/viewcontacts.php:35 mod/dfrn_poll.php:486 mod/help.php:65 #: index.php:317 mod/viewcontacts.php:35 mod/dfrn_poll.php:486 mod/help.php:65
#: mod/cal.php:44 #: mod/cal.php:44
msgid "Page not found." msgid "Page not found."
msgstr "" msgstr ""
#: index.php:431 mod/group.php:83 mod/profperm.php:29 #: index.php:435 mod/group.php:83 mod/profperm.php:29
msgid "Permission denied" msgid "Permission denied"
msgstr "" msgstr ""
#: index.php:432 include/items.php:412 mod/crepair.php:100 #: index.php:436 include/items.php:413 mod/crepair.php:100
#: mod/wallmessage.php:16 mod/wallmessage.php:40 mod/wallmessage.php:79 #: mod/wallmessage.php:16 mod/wallmessage.php:40 mod/wallmessage.php:79
#: mod/wallmessage.php:103 mod/dfrn_confirm.php:66 mod/dirfind.php:27 #: mod/wallmessage.php:103 mod/dfrn_confirm.php:67 mod/dirfind.php:27
#: mod/manage.php:131 mod/settings.php:43 mod/settings.php:149 #: mod/manage.php:131 mod/settings.php:43 mod/settings.php:149
#: mod/settings.php:665 mod/common.php:28 mod/network.php:34 mod/group.php:26 #: mod/settings.php:665 mod/common.php:28 mod/network.php:34 mod/group.php:26
#: mod/delegate.php:27 mod/delegate.php:45 mod/delegate.php:56 #: mod/delegate.php:27 mod/delegate.php:45 mod/delegate.php:56
#: mod/repair_ostatus.php:16 mod/viewcontacts.php:60 mod/unfollow.php:17 #: mod/repair_ostatus.php:16 mod/viewcontacts.php:60 mod/unfollow.php:20
#: mod/unfollow.php:59 mod/unfollow.php:93 mod/register.php:53 #: mod/unfollow.php:73 mod/unfollow.php:105 mod/register.php:53
#: mod/notifications.php:67 mod/message.php:60 mod/message.php:105 #: mod/notifications.php:67 mod/message.php:60 mod/message.php:105
#: mod/ostatus_subscribe.php:17 mod/nogroup.php:23 mod/suggest.php:61 #: mod/ostatus_subscribe.php:17 mod/nogroup.php:23 mod/suggest.php:61
#: mod/wall_upload.php:104 mod/wall_upload.php:107 mod/api.php:35 #: mod/wall_upload.php:104 mod/wall_upload.php:107 mod/api.php:35
#: mod/api.php:40 mod/profile_photo.php:29 mod/profile_photo.php:176 #: mod/api.php:40 mod/profile_photo.php:29 mod/profile_photo.php:176
#: mod/profile_photo.php:198 mod/wall_attach.php:80 mod/wall_attach.php:83 #: mod/profile_photo.php:198 mod/wall_attach.php:80 mod/wall_attach.php:83
#: mod/item.php:166 mod/uimport.php:28 mod/cal.php:306 mod/regmod.php:108 #: mod/item.php:166 mod/uimport.php:15 mod/cal.php:306 mod/regmod.php:108
#: mod/editpost.php:19 mod/fsuggest.php:80 mod/allfriends.php:23 #: mod/editpost.php:19 mod/fsuggest.php:80 mod/allfriends.php:23
#: mod/contacts.php:381 mod/events.php:193 mod/follow.php:54 mod/follow.php:118 #: mod/contacts.php:387 mod/events.php:195 mod/follow.php:54 mod/follow.php:118
#: mod/attach.php:39 mod/poke.php:145 mod/invite.php:21 mod/invite.php:112 #: mod/attach.php:39 mod/poke.php:144 mod/invite.php:21 mod/invite.php:112
#: mod/notes.php:32 mod/profiles.php:179 mod/profiles.php:511 #: mod/notes.php:32 mod/profiles.php:179 mod/profiles.php:511
#: mod/photos.php:183 mod/photos.php:1065 #: mod/photos.php:183 mod/photos.php:1067
msgid "Permission denied." msgid "Permission denied."
msgstr "" msgstr ""
#: index.php:460 #: index.php:464
msgid "toggle mobile" msgid "toggle mobile"
msgstr "" msgstr ""
@ -92,13 +92,13 @@ msgstr ""
#: view/theme/duepuntozero/config.php:71 view/theme/quattro/config.php:73 #: view/theme/duepuntozero/config.php:71 view/theme/quattro/config.php:73
#: view/theme/vier/config.php:119 view/theme/frio/config.php:118 #: view/theme/vier/config.php:119 view/theme/frio/config.php:118
#: mod/crepair.php:150 mod/install.php:206 mod/install.php:244 #: mod/crepair.php:150 mod/install.php:204 mod/install.php:242
#: mod/manage.php:184 mod/message.php:264 mod/message.php:430 #: mod/manage.php:184 mod/message.php:264 mod/message.php:430
#: mod/fsuggest.php:114 mod/contacts.php:630 mod/events.php:533 #: mod/fsuggest.php:114 mod/contacts.php:631 mod/events.php:560
#: mod/localtime.php:56 mod/poke.php:195 mod/invite.php:155 #: mod/localtime.php:56 mod/poke.php:194 mod/invite.php:155
#: mod/profiles.php:577 mod/photos.php:1094 mod/photos.php:1180 #: mod/profiles.php:577 mod/photos.php:1096 mod/photos.php:1182
#: mod/photos.php:1452 mod/photos.php:1497 mod/photos.php:1536 #: mod/photos.php:1454 mod/photos.php:1499 mod/photos.php:1538
#: mod/photos.php:1596 src/Object/Post.php:795 #: mod/photos.php:1598 src/Object/Post.php:795
msgid "Submit" msgid "Submit"
msgstr "" msgstr ""
@ -186,8 +186,8 @@ msgstr ""
#: view/theme/vier/theme.php:199 include/conversation.php:881 #: view/theme/vier/theme.php:199 include/conversation.php:881
#: mod/dirfind.php:231 mod/match.php:90 mod/suggest.php:86 #: mod/dirfind.php:231 mod/match.php:90 mod/suggest.php:86
#: mod/allfriends.php:76 mod/contacts.php:604 mod/contacts.php:610 #: mod/allfriends.php:76 mod/contacts.php:611 mod/follow.php:143
#: mod/follow.php:143 src/Model/Contact.php:933 src/Content/Widget.php:61 #: src/Model/Contact.php:944 src/Content/Widget.php:61
msgid "Connect/Follow" msgid "Connect/Follow"
msgstr "" msgstr ""
@ -195,7 +195,7 @@ msgstr ""
msgid "Examples: Robert Morgenstein, Fishing" msgid "Examples: Robert Morgenstein, Fishing"
msgstr "" msgstr ""
#: view/theme/vier/theme.php:201 mod/directory.php:214 mod/contacts.php:842 #: view/theme/vier/theme.php:201 mod/directory.php:214 mod/contacts.php:845
#: src/Content/Widget.php:63 #: src/Content/Widget.php:63
msgid "Find" msgid "Find"
msgstr "" msgstr ""
@ -226,16 +226,16 @@ msgid "Local Directory"
msgstr "" msgstr ""
#: view/theme/vier/theme.php:251 include/text.php:909 src/Content/Nav.php:151 #: view/theme/vier/theme.php:251 include/text.php:909 src/Content/Nav.php:151
#: src/Content/ForumManager.php:125 #: src/Content/ForumManager.php:130
msgid "Forums" msgid "Forums"
msgstr "" msgstr ""
#: view/theme/vier/theme.php:253 src/Content/ForumManager.php:127 #: view/theme/vier/theme.php:253 src/Content/ForumManager.php:132
msgid "External link to forum" msgid "External link to forum"
msgstr "" msgstr ""
#: view/theme/vier/theme.php:256 include/items.php:489 src/Object/Post.php:429 #: view/theme/vier/theme.php:256 include/items.php:490 src/Object/Post.php:429
#: src/App.php:786 src/Content/Widget.php:310 src/Content/ForumManager.php:130 #: src/App.php:799 src/Content/Widget.php:307 src/Content/ForumManager.php:135
msgid "show more" msgid "show more"
msgstr "" msgstr ""
@ -320,8 +320,8 @@ msgstr ""
msgid "End this session" msgid "End this session"
msgstr "" msgstr ""
#: view/theme/frio/theme.php:269 mod/contacts.php:689 mod/contacts.php:877 #: view/theme/frio/theme.php:269 mod/contacts.php:690 mod/contacts.php:880
#: src/Model/Profile.php:875 src/Content/Nav.php:100 #: src/Model/Profile.php:888 src/Content/Nav.php:100
msgid "Status" msgid "Status"
msgstr "" msgstr ""
@ -331,8 +331,8 @@ msgid "Your posts and conversations"
msgstr "" msgstr ""
#: view/theme/frio/theme.php:270 mod/newmember.php:24 mod/profperm.php:116 #: view/theme/frio/theme.php:270 mod/newmember.php:24 mod/profperm.php:116
#: mod/contacts.php:691 mod/contacts.php:893 src/Model/Profile.php:717 #: mod/contacts.php:692 mod/contacts.php:896 src/Model/Profile.php:730
#: src/Model/Profile.php:850 src/Model/Profile.php:883 src/Content/Nav.php:101 #: src/Model/Profile.php:863 src/Model/Profile.php:896 src/Content/Nav.php:101
msgid "Profile" msgid "Profile"
msgstr "" msgstr ""
@ -340,7 +340,7 @@ msgstr ""
msgid "Your profile page" msgid "Your profile page"
msgstr "" msgstr ""
#: view/theme/frio/theme.php:271 mod/fbrowser.php:35 src/Model/Profile.php:891 #: view/theme/frio/theme.php:271 mod/fbrowser.php:35 src/Model/Profile.php:904
#: src/Content/Nav.php:102 #: src/Content/Nav.php:102
msgid "Photos" msgid "Photos"
msgstr "" msgstr ""
@ -349,8 +349,8 @@ msgstr ""
msgid "Your photos" msgid "Your photos"
msgstr "" msgstr ""
#: view/theme/frio/theme.php:272 src/Model/Profile.php:899 #: view/theme/frio/theme.php:272 src/Model/Profile.php:912
#: src/Model/Profile.php:902 src/Content/Nav.php:103 #: src/Model/Profile.php:915 src/Content/Nav.php:103
msgid "Videos" msgid "Videos"
msgstr "" msgstr ""
@ -359,7 +359,7 @@ msgid "Your videos"
msgstr "" msgstr ""
#: view/theme/frio/theme.php:273 view/theme/frio/theme.php:277 mod/cal.php:276 #: view/theme/frio/theme.php:273 view/theme/frio/theme.php:277 mod/cal.php:276
#: mod/events.php:390 src/Model/Profile.php:911 src/Model/Profile.php:922 #: mod/events.php:391 src/Model/Profile.php:924 src/Model/Profile.php:935
#: src/Content/Nav.php:104 src/Content/Nav.php:170 #: src/Content/Nav.php:104 src/Content/Nav.php:170
msgid "Events" msgid "Events"
msgstr "" msgstr ""
@ -377,8 +377,8 @@ msgstr ""
msgid "Conversations from your friends" msgid "Conversations from your friends"
msgstr "" msgstr ""
#: view/theme/frio/theme.php:277 src/Model/Profile.php:914 #: view/theme/frio/theme.php:277 src/Model/Profile.php:927
#: src/Model/Profile.php:925 src/Content/Nav.php:170 #: src/Model/Profile.php:938 src/Content/Nav.php:170
msgid "Events and Calendar" msgid "Events and Calendar"
msgstr "" msgstr ""
@ -400,8 +400,8 @@ msgid "Account settings"
msgstr "" msgstr ""
#: view/theme/frio/theme.php:280 include/text.php:906 mod/viewcontacts.php:125 #: view/theme/frio/theme.php:280 include/text.php:906 mod/viewcontacts.php:125
#: mod/contacts.php:836 mod/contacts.php:905 src/Model/Profile.php:954 #: mod/contacts.php:839 mod/contacts.php:908 src/Model/Profile.php:967
#: src/Model/Profile.php:957 src/Content/Nav.php:147 src/Content/Nav.php:213 #: src/Model/Profile.php:970 src/Content/Nav.php:147 src/Content/Nav.php:213
msgid "Contacts" msgid "Contacts"
msgstr "" msgstr ""
@ -459,37 +459,37 @@ msgstr ""
msgid "%s: Updating post-type." msgid "%s: Updating post-type."
msgstr "" msgstr ""
#: include/items.php:355 mod/display.php:70 mod/display.php:245 #: include/items.php:356 mod/display.php:71 mod/display.php:254
#: mod/display.php:341 mod/admin.php:283 mod/admin.php:1963 mod/admin.php:2211 #: mod/display.php:350 mod/admin.php:283 mod/admin.php:1963 mod/admin.php:2211
#: mod/notice.php:22 mod/viewsrc.php:22 #: mod/notice.php:22 mod/viewsrc.php:22
msgid "Item not found." msgid "Item not found."
msgstr "" msgstr ""
#: include/items.php:393 #: include/items.php:394
msgid "Do you really want to delete this item?" msgid "Do you really want to delete this item?"
msgstr "" msgstr ""
#: include/items.php:395 mod/settings.php:1100 mod/settings.php:1106 #: include/items.php:396 mod/settings.php:1100 mod/settings.php:1106
#: mod/settings.php:1113 mod/settings.php:1117 mod/settings.php:1121 #: mod/settings.php:1113 mod/settings.php:1117 mod/settings.php:1121
#: mod/settings.php:1125 mod/settings.php:1129 mod/settings.php:1133 #: mod/settings.php:1125 mod/settings.php:1129 mod/settings.php:1133
#: mod/settings.php:1153 mod/settings.php:1154 mod/settings.php:1155 #: mod/settings.php:1153 mod/settings.php:1154 mod/settings.php:1155
#: mod/settings.php:1156 mod/settings.php:1157 mod/register.php:237 #: mod/settings.php:1156 mod/settings.php:1157 mod/register.php:237
#: mod/message.php:154 mod/suggest.php:40 mod/dfrn_request.php:645 #: mod/message.php:154 mod/suggest.php:40 mod/dfrn_request.php:645
#: mod/api.php:110 mod/contacts.php:465 mod/follow.php:150 mod/profiles.php:541 #: mod/api.php:110 mod/contacts.php:471 mod/follow.php:150 mod/profiles.php:541
#: mod/profiles.php:544 mod/profiles.php:566 #: mod/profiles.php:544 mod/profiles.php:566
msgid "Yes" msgid "Yes"
msgstr "" msgstr ""
#: include/items.php:398 include/conversation.php:1179 mod/videos.php:146 #: include/items.php:399 include/conversation.php:1179 mod/videos.php:146
#: mod/settings.php:676 mod/settings.php:702 mod/unfollow.php:120 #: mod/settings.php:676 mod/settings.php:702 mod/unfollow.php:130
#: mod/message.php:157 mod/tagrm.php:19 mod/tagrm.php:91 mod/suggest.php:43 #: mod/message.php:157 mod/tagrm.php:19 mod/tagrm.php:91 mod/suggest.php:43
#: mod/dfrn_request.php:655 mod/editpost.php:140 mod/contacts.php:468 #: mod/dfrn_request.php:655 mod/editpost.php:146 mod/contacts.php:474
#: mod/follow.php:161 mod/fbrowser.php:104 mod/fbrowser.php:135 #: mod/follow.php:161 mod/fbrowser.php:104 mod/fbrowser.php:135
#: mod/photos.php:255 mod/photos.php:327 #: mod/photos.php:255 mod/photos.php:327
msgid "Cancel" msgid "Cancel"
msgstr "" msgstr ""
#: include/items.php:483 src/Content/Feature.php:96 #: include/items.php:484 src/Content/Feature.php:96
msgid "Archives" msgid "Archives"
msgstr "" msgstr ""
@ -558,35 +558,35 @@ msgstr ""
msgid "%1$s marked %2$s's %3$s as favorite" msgid "%1$s marked %2$s's %3$s as favorite"
msgstr "" msgstr ""
#: include/conversation.php:545 mod/profiles.php:352 mod/photos.php:1507 #: include/conversation.php:545 mod/profiles.php:352 mod/photos.php:1509
msgid "Likes" msgid "Likes"
msgstr "" msgstr ""
#: include/conversation.php:545 mod/profiles.php:356 mod/photos.php:1507 #: include/conversation.php:545 mod/profiles.php:356 mod/photos.php:1509
msgid "Dislikes" msgid "Dislikes"
msgstr "" msgstr ""
#: include/conversation.php:546 include/conversation.php:1492 #: include/conversation.php:546 include/conversation.php:1492
#: mod/photos.php:1508 #: mod/photos.php:1510
msgid "Attending" msgid "Attending"
msgid_plural "Attending" msgid_plural "Attending"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#: include/conversation.php:546 mod/photos.php:1508 #: include/conversation.php:546 mod/photos.php:1510
msgid "Not attending" msgid "Not attending"
msgstr "" msgstr ""
#: include/conversation.php:546 mod/photos.php:1508 #: include/conversation.php:546 mod/photos.php:1510
msgid "Might attend" msgid "Might attend"
msgstr "" msgstr ""
#: include/conversation.php:626 mod/photos.php:1564 src/Object/Post.php:195 #: include/conversation.php:626 mod/photos.php:1566 src/Object/Post.php:195
msgid "Select" msgid "Select"
msgstr "" msgstr ""
#: include/conversation.php:627 mod/settings.php:736 mod/admin.php:1906 #: include/conversation.php:627 mod/settings.php:736 mod/admin.php:1906
#: mod/contacts.php:852 mod/contacts.php:1130 mod/photos.php:1565 #: mod/contacts.php:855 mod/contacts.php:1133 mod/photos.php:1567
msgid "Delete" msgid "Delete"
msgstr "" msgstr ""
@ -614,7 +614,7 @@ msgstr ""
#: include/conversation.php:698 include/conversation.php:1160 #: include/conversation.php:698 include/conversation.php:1160
#: mod/wallmessage.php:145 mod/message.php:263 mod/message.php:431 #: mod/wallmessage.php:145 mod/message.php:263 mod/message.php:431
#: mod/editpost.php:115 mod/photos.php:1480 src/Object/Post.php:401 #: mod/editpost.php:121 mod/photos.php:1482 src/Object/Post.php:401
msgid "Please wait" msgid "Please wait"
msgstr "" msgstr ""
@ -626,36 +626,36 @@ msgstr ""
msgid "Delete Selected Items" msgid "Delete Selected Items"
msgstr "" msgstr ""
#: include/conversation.php:867 src/Model/Contact.php:937 #: include/conversation.php:867 src/Model/Contact.php:948
msgid "View Status" msgid "View Status"
msgstr "" msgstr ""
#: include/conversation.php:868 include/conversation.php:884 #: include/conversation.php:868 include/conversation.php:884
#: mod/dirfind.php:230 mod/directory.php:164 mod/match.php:89 #: mod/dirfind.php:230 mod/directory.php:164 mod/match.php:89
#: mod/suggest.php:85 mod/allfriends.php:75 src/Model/Contact.php:877 #: mod/suggest.php:85 mod/allfriends.php:75 src/Model/Contact.php:888
#: src/Model/Contact.php:930 src/Model/Contact.php:938 #: src/Model/Contact.php:941 src/Model/Contact.php:949
msgid "View Profile" msgid "View Profile"
msgstr "" msgstr ""
#: include/conversation.php:869 src/Model/Contact.php:939 #: include/conversation.php:869 src/Model/Contact.php:950
msgid "View Photos" msgid "View Photos"
msgstr "" msgstr ""
#: include/conversation.php:870 src/Model/Contact.php:931 #: include/conversation.php:870 src/Model/Contact.php:942
#: src/Model/Contact.php:940 #: src/Model/Contact.php:951
msgid "Network Posts" msgid "Network Posts"
msgstr "" msgstr ""
#: include/conversation.php:871 src/Model/Contact.php:932 #: include/conversation.php:871 src/Model/Contact.php:943
#: src/Model/Contact.php:941 #: src/Model/Contact.php:952
msgid "View Contact" msgid "View Contact"
msgstr "" msgstr ""
#: include/conversation.php:872 src/Model/Contact.php:943 #: include/conversation.php:872 src/Model/Contact.php:954
msgid "Send PM" msgid "Send PM"
msgstr "" msgstr ""
#: include/conversation.php:876 src/Model/Contact.php:944 #: include/conversation.php:876 src/Model/Contact.php:955
msgid "Poke" msgid "Poke"
msgstr "" msgstr ""
@ -786,85 +786,85 @@ msgid "Share"
msgstr "" msgstr ""
#: include/conversation.php:1142 mod/wallmessage.php:143 mod/message.php:261 #: include/conversation.php:1142 mod/wallmessage.php:143 mod/message.php:261
#: mod/message.php:428 mod/editpost.php:101 #: mod/message.php:428 mod/editpost.php:107
msgid "Upload photo" msgid "Upload photo"
msgstr "" msgstr ""
#: include/conversation.php:1143 mod/editpost.php:102 #: include/conversation.php:1143 mod/editpost.php:108
msgid "upload photo" msgid "upload photo"
msgstr "" msgstr ""
#: include/conversation.php:1144 mod/editpost.php:103 #: include/conversation.php:1144 mod/editpost.php:109
msgid "Attach file" msgid "Attach file"
msgstr "" msgstr ""
#: include/conversation.php:1145 mod/editpost.php:104 #: include/conversation.php:1145 mod/editpost.php:110
msgid "attach file" msgid "attach file"
msgstr "" msgstr ""
#: include/conversation.php:1146 mod/wallmessage.php:144 mod/message.php:262 #: include/conversation.php:1146 mod/wallmessage.php:144 mod/message.php:262
#: mod/message.php:429 mod/editpost.php:105 #: mod/message.php:429 mod/editpost.php:111
msgid "Insert web link" msgid "Insert web link"
msgstr "" msgstr ""
#: include/conversation.php:1147 mod/editpost.php:106 #: include/conversation.php:1147 mod/editpost.php:112
msgid "web link" msgid "web link"
msgstr "" msgstr ""
#: include/conversation.php:1148 mod/editpost.php:107 #: include/conversation.php:1148 mod/editpost.php:113
msgid "Insert video link" msgid "Insert video link"
msgstr "" msgstr ""
#: include/conversation.php:1149 mod/editpost.php:108 #: include/conversation.php:1149 mod/editpost.php:114
msgid "video link" msgid "video link"
msgstr "" msgstr ""
#: include/conversation.php:1150 mod/editpost.php:109 #: include/conversation.php:1150 mod/editpost.php:115
msgid "Insert audio link" msgid "Insert audio link"
msgstr "" msgstr ""
#: include/conversation.php:1151 mod/editpost.php:110 #: include/conversation.php:1151 mod/editpost.php:116
msgid "audio link" msgid "audio link"
msgstr "" msgstr ""
#: include/conversation.php:1152 mod/editpost.php:111 #: include/conversation.php:1152 mod/editpost.php:117
msgid "Set your location" msgid "Set your location"
msgstr "" msgstr ""
#: include/conversation.php:1153 mod/editpost.php:112 #: include/conversation.php:1153 mod/editpost.php:118
msgid "set location" msgid "set location"
msgstr "" msgstr ""
#: include/conversation.php:1154 mod/editpost.php:113 #: include/conversation.php:1154 mod/editpost.php:119
msgid "Clear browser location" msgid "Clear browser location"
msgstr "" msgstr ""
#: include/conversation.php:1155 mod/editpost.php:114 #: include/conversation.php:1155 mod/editpost.php:120
msgid "clear location" msgid "clear location"
msgstr "" msgstr ""
#: include/conversation.php:1157 mod/editpost.php:129 #: include/conversation.php:1157 mod/editpost.php:135
msgid "Set title" msgid "Set title"
msgstr "" msgstr ""
#: include/conversation.php:1159 mod/editpost.php:131 #: include/conversation.php:1159 mod/editpost.php:137
msgid "Categories (comma-separated list)" msgid "Categories (comma-separated list)"
msgstr "" msgstr ""
#: include/conversation.php:1161 mod/editpost.php:116 #: include/conversation.php:1161 mod/editpost.php:122
msgid "Permission settings" msgid "Permission settings"
msgstr "" msgstr ""
#: include/conversation.php:1162 mod/editpost.php:146 #: include/conversation.php:1162 mod/editpost.php:152
msgid "permissions" msgid "permissions"
msgstr "" msgstr ""
#: include/conversation.php:1171 mod/editpost.php:126 #: include/conversation.php:1171 mod/editpost.php:132
msgid "Public post" msgid "Public post"
msgstr "" msgstr ""
#: include/conversation.php:1175 mod/editpost.php:137 mod/events.php:531 #: include/conversation.php:1175 mod/editpost.php:143 mod/events.php:558
#: mod/photos.php:1498 mod/photos.php:1537 mod/photos.php:1597 #: mod/photos.php:1500 mod/photos.php:1539 mod/photos.php:1599
#: src/Object/Post.php:804 #: src/Object/Post.php:804
msgid "Preview" msgid "Preview"
msgstr "" msgstr ""
@ -881,11 +881,11 @@ msgstr ""
msgid "Private post" msgid "Private post"
msgstr "" msgstr ""
#: include/conversation.php:1191 mod/editpost.php:144 src/Model/Profile.php:344 #: include/conversation.php:1191 mod/editpost.php:150 src/Model/Profile.php:357
msgid "Message" msgid "Message"
msgstr "" msgstr ""
#: include/conversation.php:1192 mod/editpost.php:145 #: include/conversation.php:1192 mod/editpost.php:151
msgid "Browser" msgid "Browser"
msgstr "" msgstr ""
@ -911,7 +911,7 @@ msgid_plural "Not Attending"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#: include/conversation.php:1498 src/Content/ContactSelector.php:122 #: include/conversation.php:1498 src/Content/ContactSelector.php:127
msgid "Undecided" msgid "Undecided"
msgid_plural "Undecided" msgid_plural "Undecided"
msgstr[0] "" msgstr[0] ""
@ -1276,7 +1276,7 @@ msgstr[1] ""
msgid "View Contacts" msgid "View Contacts"
msgstr "" msgstr ""
#: include/text.php:889 mod/filer.php:35 mod/editpost.php:100 mod/notes.php:54 #: include/text.php:889 mod/filer.php:35 mod/editpost.php:106 mod/notes.php:54
msgid "Save" msgid "Save"
msgstr "" msgstr ""
@ -1349,132 +1349,132 @@ msgstr ""
msgid "rebuffed" msgid "rebuffed"
msgstr "" msgstr ""
#: include/text.php:972 mod/settings.php:941 src/Model/Event.php:388 #: include/text.php:972 mod/settings.php:941 src/Model/Event.php:389
msgid "Monday" msgid "Monday"
msgstr "" msgstr ""
#: include/text.php:972 src/Model/Event.php:389 #: include/text.php:972 src/Model/Event.php:390
msgid "Tuesday" msgid "Tuesday"
msgstr "" msgstr ""
#: include/text.php:972 src/Model/Event.php:390 #: include/text.php:972 src/Model/Event.php:391
msgid "Wednesday" msgid "Wednesday"
msgstr "" msgstr ""
#: include/text.php:972 src/Model/Event.php:391 #: include/text.php:972 src/Model/Event.php:392
msgid "Thursday" msgid "Thursday"
msgstr "" msgstr ""
#: include/text.php:972 src/Model/Event.php:392 #: include/text.php:972 src/Model/Event.php:393
msgid "Friday" msgid "Friday"
msgstr "" msgstr ""
#: include/text.php:972 src/Model/Event.php:393 #: include/text.php:972 src/Model/Event.php:394
msgid "Saturday" msgid "Saturday"
msgstr "" msgstr ""
#: include/text.php:972 mod/settings.php:941 src/Model/Event.php:387 #: include/text.php:972 mod/settings.php:941 src/Model/Event.php:388
msgid "Sunday" msgid "Sunday"
msgstr "" msgstr ""
#: include/text.php:976 src/Model/Event.php:408 #: include/text.php:976 src/Model/Event.php:409
msgid "January" msgid "January"
msgstr "" msgstr ""
#: include/text.php:976 src/Model/Event.php:409 #: include/text.php:976 src/Model/Event.php:410
msgid "February" msgid "February"
msgstr "" msgstr ""
#: include/text.php:976 src/Model/Event.php:410 #: include/text.php:976 src/Model/Event.php:411
msgid "March" msgid "March"
msgstr "" msgstr ""
#: include/text.php:976 src/Model/Event.php:411 #: include/text.php:976 src/Model/Event.php:412
msgid "April" msgid "April"
msgstr "" msgstr ""
#: include/text.php:976 include/text.php:993 src/Model/Event.php:399 #: include/text.php:976 include/text.php:993 src/Model/Event.php:400
#: src/Model/Event.php:412 #: src/Model/Event.php:413
msgid "May" msgid "May"
msgstr "" msgstr ""
#: include/text.php:976 src/Model/Event.php:413 #: include/text.php:976 src/Model/Event.php:414
msgid "June" msgid "June"
msgstr "" msgstr ""
#: include/text.php:976 src/Model/Event.php:414 #: include/text.php:976 src/Model/Event.php:415
msgid "July" msgid "July"
msgstr "" msgstr ""
#: include/text.php:976 src/Model/Event.php:415 #: include/text.php:976 src/Model/Event.php:416
msgid "August" msgid "August"
msgstr "" msgstr ""
#: include/text.php:976 src/Model/Event.php:416 #: include/text.php:976 src/Model/Event.php:417
msgid "September" msgid "September"
msgstr "" msgstr ""
#: include/text.php:976 src/Model/Event.php:417 #: include/text.php:976 src/Model/Event.php:418
msgid "October" msgid "October"
msgstr "" msgstr ""
#: include/text.php:976 src/Model/Event.php:418 #: include/text.php:976 src/Model/Event.php:419
msgid "November" msgid "November"
msgstr "" msgstr ""
#: include/text.php:976 src/Model/Event.php:419 #: include/text.php:976 src/Model/Event.php:420
msgid "December" msgid "December"
msgstr "" msgstr ""
#: include/text.php:990 src/Model/Event.php:380 #: include/text.php:990 src/Model/Event.php:381
msgid "Mon" msgid "Mon"
msgstr "" msgstr ""
#: include/text.php:990 src/Model/Event.php:381 #: include/text.php:990 src/Model/Event.php:382
msgid "Tue" msgid "Tue"
msgstr "" msgstr ""
#: include/text.php:990 src/Model/Event.php:382 #: include/text.php:990 src/Model/Event.php:383
msgid "Wed" msgid "Wed"
msgstr "" msgstr ""
#: include/text.php:990 src/Model/Event.php:383 #: include/text.php:990 src/Model/Event.php:384
msgid "Thu" msgid "Thu"
msgstr "" msgstr ""
#: include/text.php:990 src/Model/Event.php:384 #: include/text.php:990 src/Model/Event.php:385
msgid "Fri" msgid "Fri"
msgstr "" msgstr ""
#: include/text.php:990 src/Model/Event.php:385 #: include/text.php:990 src/Model/Event.php:386
msgid "Sat" msgid "Sat"
msgstr "" msgstr ""
#: include/text.php:990 src/Model/Event.php:379 #: include/text.php:990 src/Model/Event.php:380
msgid "Sun" msgid "Sun"
msgstr "" msgstr ""
#: include/text.php:993 src/Model/Event.php:395 #: include/text.php:993 src/Model/Event.php:396
msgid "Jan" msgid "Jan"
msgstr "" msgstr ""
#: include/text.php:993 src/Model/Event.php:396 #: include/text.php:993 src/Model/Event.php:397
msgid "Feb" msgid "Feb"
msgstr "" msgstr ""
#: include/text.php:993 src/Model/Event.php:397 #: include/text.php:993 src/Model/Event.php:398
msgid "Mar" msgid "Mar"
msgstr "" msgstr ""
#: include/text.php:993 src/Model/Event.php:398 #: include/text.php:993 src/Model/Event.php:399
msgid "Apr" msgid "Apr"
msgstr "" msgstr ""
#: include/text.php:993 src/Model/Event.php:401 #: include/text.php:993 src/Model/Event.php:402
msgid "Jul" msgid "Jul"
msgstr "" msgstr ""
#: include/text.php:993 src/Model/Event.php:402 #: include/text.php:993 src/Model/Event.php:403
msgid "Aug" msgid "Aug"
msgstr "" msgstr ""
@ -1482,15 +1482,15 @@ msgstr ""
msgid "Sep" msgid "Sep"
msgstr "" msgstr ""
#: include/text.php:993 src/Model/Event.php:404 #: include/text.php:993 src/Model/Event.php:405
msgid "Oct" msgid "Oct"
msgstr "" msgstr ""
#: include/text.php:993 src/Model/Event.php:405 #: include/text.php:993 src/Model/Event.php:406
msgid "Nov" msgid "Nov"
msgstr "" msgstr ""
#: include/text.php:993 src/Model/Event.php:406 #: include/text.php:993 src/Model/Event.php:407
msgid "Dec" msgid "Dec"
msgstr "" msgstr ""
@ -1499,7 +1499,7 @@ msgstr ""
msgid "Content warning: %s" msgid "Content warning: %s"
msgstr "" msgstr ""
#: include/text.php:1204 mod/videos.php:375 #: include/text.php:1204 mod/videos.php:376
msgid "View Video" msgid "View Video"
msgstr "" msgstr ""
@ -1519,7 +1519,7 @@ msgstr ""
msgid "view on separate page" msgid "view on separate page"
msgstr "" msgstr ""
#: include/text.php:1421 include/text.php:1428 src/Model/Event.php:609 #: include/text.php:1421 include/text.php:1428 src/Model/Event.php:616
msgid "link to source" msgid "link to source"
msgstr "" msgstr ""
@ -1541,30 +1541,30 @@ msgstr ""
msgid "Item filed" msgid "Item filed"
msgstr "" msgstr ""
#: include/api.php:1138 #: include/api.php:1140
#, php-format #, php-format
msgid "Daily posting limit of %d post reached. The post was rejected." msgid "Daily posting limit of %d post reached. The post was rejected."
msgid_plural "Daily posting limit of %d posts reached. The post was rejected." msgid_plural "Daily posting limit of %d posts reached. The post was rejected."
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#: include/api.php:1152 #: include/api.php:1154
#, php-format #, php-format
msgid "Weekly posting limit of %d post reached. The post was rejected." msgid "Weekly posting limit of %d post reached. The post was rejected."
msgid_plural "Weekly posting limit of %d posts reached. The post was rejected." msgid_plural "Weekly posting limit of %d posts reached. The post was rejected."
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#: include/api.php:1166 #: include/api.php:1168
#, php-format #, php-format
msgid "Monthly posting limit of %d post reached. The post was rejected." msgid "Monthly posting limit of %d post reached. The post was rejected."
msgstr "" msgstr ""
#: include/api.php:4233 mod/profile_photo.php:84 mod/profile_photo.php:93 #: include/api.php:4240 mod/profile_photo.php:84 mod/profile_photo.php:93
#: mod/profile_photo.php:102 mod/profile_photo.php:211 #: mod/profile_photo.php:102 mod/profile_photo.php:211
#: mod/profile_photo.php:300 mod/profile_photo.php:310 mod/photos.php:90 #: mod/profile_photo.php:300 mod/profile_photo.php:310 mod/photos.php:90
#: mod/photos.php:198 mod/photos.php:735 mod/photos.php:1169 #: mod/photos.php:198 mod/photos.php:735 mod/photos.php:1171
#: mod/photos.php:1186 mod/photos.php:1678 src/Model/User.php:595 #: mod/photos.php:1188 mod/photos.php:1680 src/Model/User.php:595
#: src/Model/User.php:603 src/Model/User.php:611 #: src/Model/User.php:603 src/Model/User.php:611
msgid "Profile Photos" msgid "Profile Photos"
msgstr "" msgstr ""
@ -1578,7 +1578,7 @@ msgid "Contact update failed."
msgstr "" msgstr ""
#: mod/crepair.php:112 mod/redir.php:29 mod/redir.php:127 #: mod/crepair.php:112 mod/redir.php:29 mod/redir.php:127
#: mod/dfrn_confirm.php:127 mod/fsuggest.php:30 mod/fsuggest.php:96 #: mod/dfrn_confirm.php:128 mod/fsuggest.php:30 mod/fsuggest.php:96
msgid "Contact not found." msgid "Contact not found."
msgstr "" msgstr ""
@ -1719,143 +1719,143 @@ msgstr ""
msgid "Your message:" msgid "Your message:"
msgstr "" msgstr ""
#: mod/lockview.php:42 mod/lockview.php:50 #: mod/lockview.php:46 mod/lockview.php:57
msgid "Remote privacy information not available." msgid "Remote privacy information not available."
msgstr "" msgstr ""
#: mod/lockview.php:59 #: mod/lockview.php:66
msgid "Visible to:" msgid "Visible to:"
msgstr "" msgstr ""
#: mod/install.php:100 #: mod/install.php:98
msgid "Friendica Communications Server - Setup" msgid "Friendica Communications Server - Setup"
msgstr "" msgstr ""
#: mod/install.php:106 #: mod/install.php:104
msgid "Could not connect to database." msgid "Could not connect to database."
msgstr "" msgstr ""
#: mod/install.php:110 #: mod/install.php:108
msgid "Could not create table." msgid "Could not create table."
msgstr "" msgstr ""
#: mod/install.php:116 #: mod/install.php:114
msgid "Your Friendica site database has been installed." msgid "Your Friendica site database has been installed."
msgstr "" msgstr ""
#: mod/install.php:121 #: mod/install.php:119
msgid "" msgid ""
"You may need to import the file \"database.sql\" manually using phpmyadmin " "You may need to import the file \"database.sql\" manually using phpmyadmin "
"or mysql." "or mysql."
msgstr "" msgstr ""
#: mod/install.php:122 mod/install.php:166 mod/install.php:274 #: mod/install.php:120 mod/install.php:164 mod/install.php:272
msgid "Please see the file \"INSTALL.txt\"." msgid "Please see the file \"INSTALL.txt\"."
msgstr "" msgstr ""
#: mod/install.php:134 #: mod/install.php:132
msgid "Database already in use." msgid "Database already in use."
msgstr "" msgstr ""
#: mod/install.php:163 #: mod/install.php:161
msgid "System check" msgid "System check"
msgstr "" msgstr ""
#: mod/install.php:167 mod/cal.php:279 mod/events.php:394 #: mod/install.php:165 mod/cal.php:279 mod/events.php:395
msgid "Next" msgid "Next"
msgstr "" msgstr ""
#: mod/install.php:168 #: mod/install.php:166
msgid "Check again" msgid "Check again"
msgstr "" msgstr ""
#: mod/install.php:187 #: mod/install.php:185
msgid "Database connection" msgid "Database connection"
msgstr "" msgstr ""
#: mod/install.php:188 #: mod/install.php:186
msgid "" msgid ""
"In order to install Friendica we need to know how to connect to your " "In order to install Friendica we need to know how to connect to your "
"database." "database."
msgstr "" msgstr ""
#: mod/install.php:189 #: mod/install.php:187
msgid "" msgid ""
"Please contact your hosting provider or site administrator if you have " "Please contact your hosting provider or site administrator if you have "
"questions about these settings." "questions about these settings."
msgstr "" msgstr ""
#: mod/install.php:190 #: mod/install.php:188
msgid "" msgid ""
"The database you specify below should already exist. If it does not, please " "The database you specify below should already exist. If it does not, please "
"create it before continuing." "create it before continuing."
msgstr "" msgstr ""
#: mod/install.php:194 #: mod/install.php:192
msgid "Database Server Name" msgid "Database Server Name"
msgstr "" msgstr ""
#: mod/install.php:195 #: mod/install.php:193
msgid "Database Login Name" msgid "Database Login Name"
msgstr "" msgstr ""
#: mod/install.php:196 #: mod/install.php:194
msgid "Database Login Password" msgid "Database Login Password"
msgstr "" msgstr ""
#: mod/install.php:196 #: mod/install.php:194
msgid "For security reasons the password must not be empty" msgid "For security reasons the password must not be empty"
msgstr "" msgstr ""
#: mod/install.php:197 #: mod/install.php:195
msgid "Database Name" msgid "Database Name"
msgstr "" msgstr ""
#: mod/install.php:198 mod/install.php:235 #: mod/install.php:196 mod/install.php:233
msgid "Site administrator email address" msgid "Site administrator email address"
msgstr "" msgstr ""
#: mod/install.php:198 mod/install.php:235 #: mod/install.php:196 mod/install.php:233
msgid "" msgid ""
"Your account email address must match this in order to use the web admin " "Your account email address must match this in order to use the web admin "
"panel." "panel."
msgstr "" msgstr ""
#: mod/install.php:200 mod/install.php:238 #: mod/install.php:198 mod/install.php:236
msgid "Please select a default timezone for your website" msgid "Please select a default timezone for your website"
msgstr "" msgstr ""
#: mod/install.php:225 #: mod/install.php:223
msgid "Site settings" msgid "Site settings"
msgstr "" msgstr ""
#: mod/install.php:239 #: mod/install.php:237
msgid "System Language:" msgid "System Language:"
msgstr "" msgstr ""
#: mod/install.php:239 #: mod/install.php:237
msgid "" msgid ""
"Set the default language for your Friendica installation interface and to " "Set the default language for your Friendica installation interface and to "
"send emails." "send emails."
msgstr "" msgstr ""
#: mod/install.php:255 #: mod/install.php:253
msgid "" msgid ""
"The database configuration file \"config/local.ini.php\" could not be " "The database configuration file \"config/local.ini.php\" could not be "
"written. Please use the enclosed text to create a configuration file in your " "written. Please use the enclosed text to create a configuration file in your "
"web server root." "web server root."
msgstr "" msgstr ""
#: mod/install.php:272 #: mod/install.php:270
msgid "<h1>What next</h1>" msgid "<h1>What next</h1>"
msgstr "" msgstr ""
#: mod/install.php:273 #: mod/install.php:271
msgid "" msgid ""
"IMPORTANT: You will need to [manually] setup a scheduled task for the worker." "IMPORTANT: You will need to [manually] setup a scheduled task for the worker."
msgstr "" msgstr ""
#: mod/install.php:276 #: mod/install.php:274
#, php-format #, php-format
msgid "" msgid ""
"Go to your new Friendica node <a href=\"%s/register\">registration page</a> " "Go to your new Friendica node <a href=\"%s/register\">registration page</a> "
@ -1863,82 +1863,82 @@ msgid ""
"administrator email. This will allow you to enter the site admin panel." "administrator email. This will allow you to enter the site admin panel."
msgstr "" msgstr ""
#: mod/dfrn_confirm.php:72 mod/profiles.php:38 mod/profiles.php:148 #: mod/dfrn_confirm.php:73 mod/profiles.php:38 mod/profiles.php:148
#: mod/profiles.php:193 mod/profiles.php:523 #: mod/profiles.php:193 mod/profiles.php:523
msgid "Profile not found." msgid "Profile not found."
msgstr "" msgstr ""
#: mod/dfrn_confirm.php:128 #: mod/dfrn_confirm.php:129
msgid "" msgid ""
"This may occasionally happen if contact was requested by both persons and it " "This may occasionally happen if contact was requested by both persons and it "
"has already been approved." "has already been approved."
msgstr "" msgstr ""
#: mod/dfrn_confirm.php:238 #: mod/dfrn_confirm.php:239
msgid "Response from remote site was not understood." msgid "Response from remote site was not understood."
msgstr "" msgstr ""
#: mod/dfrn_confirm.php:245 mod/dfrn_confirm.php:251 #: mod/dfrn_confirm.php:246 mod/dfrn_confirm.php:252
msgid "Unexpected response from remote site: " msgid "Unexpected response from remote site: "
msgstr "" msgstr ""
#: mod/dfrn_confirm.php:260 #: mod/dfrn_confirm.php:261
msgid "Confirmation completed successfully." msgid "Confirmation completed successfully."
msgstr "" msgstr ""
#: mod/dfrn_confirm.php:272 #: mod/dfrn_confirm.php:273
msgid "Temporary failure. Please wait and try again." msgid "Temporary failure. Please wait and try again."
msgstr "" msgstr ""
#: mod/dfrn_confirm.php:275 #: mod/dfrn_confirm.php:276
msgid "Introduction failed or was revoked." msgid "Introduction failed or was revoked."
msgstr "" msgstr ""
#: mod/dfrn_confirm.php:280 #: mod/dfrn_confirm.php:281
msgid "Remote site reported: " msgid "Remote site reported: "
msgstr "" msgstr ""
#: mod/dfrn_confirm.php:392 #: mod/dfrn_confirm.php:382
msgid "Unable to set contact photo." msgid "Unable to set contact photo."
msgstr "" msgstr ""
#: mod/dfrn_confirm.php:450 #: mod/dfrn_confirm.php:444
#, php-format #, php-format
msgid "No user record found for '%s' " msgid "No user record found for '%s' "
msgstr "" msgstr ""
#: mod/dfrn_confirm.php:460 #: mod/dfrn_confirm.php:454
msgid "Our site encryption key is apparently messed up." msgid "Our site encryption key is apparently messed up."
msgstr "" msgstr ""
#: mod/dfrn_confirm.php:471 #: mod/dfrn_confirm.php:465
msgid "Empty site URL was provided or URL could not be decrypted by us." msgid "Empty site URL was provided or URL could not be decrypted by us."
msgstr "" msgstr ""
#: mod/dfrn_confirm.php:487 #: mod/dfrn_confirm.php:481
msgid "Contact record was not found for you on our site." msgid "Contact record was not found for you on our site."
msgstr "" msgstr ""
#: mod/dfrn_confirm.php:501 #: mod/dfrn_confirm.php:495
#, php-format #, php-format
msgid "Site public key not available in contact record for URL %s." msgid "Site public key not available in contact record for URL %s."
msgstr "" msgstr ""
#: mod/dfrn_confirm.php:517 #: mod/dfrn_confirm.php:511
msgid "" msgid ""
"The ID provided by your system is a duplicate on our system. It should work " "The ID provided by your system is a duplicate on our system. It should work "
"if you try again." "if you try again."
msgstr "" msgstr ""
#: mod/dfrn_confirm.php:528 #: mod/dfrn_confirm.php:522
msgid "Unable to set your contact credentials on our system." msgid "Unable to set your contact credentials on our system."
msgstr "" msgstr ""
#: mod/dfrn_confirm.php:584 #: mod/dfrn_confirm.php:578
msgid "Unable to update your contact profile details on our system" msgid "Unable to update your contact profile details on our system"
msgstr "" msgstr ""
#: mod/dfrn_confirm.php:614 mod/dfrn_request.php:561 src/Model/Contact.php:1891 #: mod/dfrn_confirm.php:608 mod/dfrn_request.php:561 src/Model/Contact.php:1909
msgid "[Name Withheld]" msgid "[Name Withheld]"
msgstr "" msgstr ""
@ -1953,7 +1953,7 @@ msgid "Forum Search - %s"
msgstr "" msgstr ""
#: mod/dirfind.php:221 mod/match.php:105 mod/suggest.php:104 #: mod/dirfind.php:221 mod/match.php:105 mod/suggest.php:104
#: mod/allfriends.php:92 src/Model/Profile.php:292 src/Content/Widget.php:37 #: mod/allfriends.php:92 src/Model/Profile.php:305 src/Content/Widget.php:37
msgid "Connect" msgid "Connect"
msgstr "" msgstr ""
@ -1985,8 +1985,8 @@ msgstr ""
#: mod/videos.php:198 mod/webfinger.php:16 mod/directory.php:42 #: mod/videos.php:198 mod/webfinger.php:16 mod/directory.php:42
#: mod/search.php:105 mod/search.php:111 mod/viewcontacts.php:48 #: mod/search.php:105 mod/search.php:111 mod/viewcontacts.php:48
#: mod/display.php:194 mod/dfrn_request.php:599 mod/probe.php:13 #: mod/display.php:203 mod/dfrn_request.php:599 mod/probe.php:13
#: mod/community.php:28 mod/photos.php:945 #: mod/community.php:28 mod/photos.php:947
msgid "Public access denied." msgid "Public access denied."
msgstr "" msgstr ""
@ -1994,19 +1994,19 @@ msgstr ""
msgid "No videos selected" msgid "No videos selected"
msgstr "" msgstr ""
#: mod/videos.php:307 mod/photos.php:1050 #: mod/videos.php:307 mod/photos.php:1052
msgid "Access to this item is restricted." msgid "Access to this item is restricted."
msgstr "" msgstr ""
#: mod/videos.php:382 mod/photos.php:1699 #: mod/videos.php:383 mod/photos.php:1701
msgid "View Album" msgid "View Album"
msgstr "" msgstr ""
#: mod/videos.php:390 #: mod/videos.php:391
msgid "Recent Videos" msgid "Recent Videos"
msgstr "" msgstr ""
#: mod/videos.php:392 #: mod/videos.php:393
msgid "Upload New Videos" msgid "Upload New Videos"
msgstr "" msgstr ""
@ -2014,27 +2014,27 @@ msgstr ""
msgid "Only logged in users are permitted to perform a probing." msgid "Only logged in users are permitted to perform a probing."
msgstr "" msgstr ""
#: mod/directory.php:151 mod/notifications.php:253 mod/contacts.php:680 #: mod/directory.php:151 mod/notifications.php:248 mod/contacts.php:681
#: mod/events.php:521 src/Model/Event.php:66 src/Model/Event.php:93 #: mod/events.php:548 src/Model/Event.php:67 src/Model/Event.php:94
#: src/Model/Event.php:430 src/Model/Event.php:915 src/Model/Profile.php:417 #: src/Model/Event.php:431 src/Model/Event.php:922 src/Model/Profile.php:430
msgid "Location:" msgid "Location:"
msgstr "" msgstr ""
#: mod/directory.php:156 mod/notifications.php:259 src/Model/Profile.php:420 #: mod/directory.php:156 mod/notifications.php:254 src/Model/Profile.php:433
#: src/Model/Profile.php:732 #: src/Model/Profile.php:745
msgid "Gender:" msgid "Gender:"
msgstr "" msgstr ""
#: mod/directory.php:157 src/Model/Profile.php:421 src/Model/Profile.php:756 #: mod/directory.php:157 src/Model/Profile.php:434 src/Model/Profile.php:769
msgid "Status:" msgid "Status:"
msgstr "" msgstr ""
#: mod/directory.php:158 src/Model/Profile.php:422 src/Model/Profile.php:773 #: mod/directory.php:158 src/Model/Profile.php:435 src/Model/Profile.php:786
msgid "Homepage:" msgid "Homepage:"
msgstr "" msgstr ""
#: mod/directory.php:159 mod/notifications.php:255 mod/contacts.php:684 #: mod/directory.php:159 mod/notifications.php:250 mod/contacts.php:685
#: src/Model/Profile.php:423 src/Model/Profile.php:793 #: src/Model/Profile.php:436 src/Model/Profile.php:806
msgid "About:" msgid "About:"
msgstr "" msgstr ""
@ -2074,7 +2074,7 @@ msgstr ""
msgid "Account" msgid "Account"
msgstr "" msgstr ""
#: mod/settings.php:64 src/Model/Profile.php:372 src/Content/Nav.php:210 #: mod/settings.php:64 src/Model/Profile.php:385 src/Content/Nav.php:210
msgid "Profiles" msgid "Profiles"
msgstr "" msgstr ""
@ -2114,7 +2114,7 @@ msgstr ""
msgid "Missing some important data!" msgid "Missing some important data!"
msgstr "" msgstr ""
#: mod/settings.php:176 mod/settings.php:701 mod/contacts.php:848 #: mod/settings.php:176 mod/settings.php:701 mod/contacts.php:851
msgid "Update" msgid "Update"
msgstr "" msgstr ""
@ -2803,7 +2803,7 @@ msgstr ""
msgid "Basic Settings" msgid "Basic Settings"
msgstr "" msgstr ""
#: mod/settings.php:1204 src/Model/Profile.php:725 #: mod/settings.php:1204 src/Model/Profile.php:738
msgid "Full Name:" msgid "Full Name:"
msgstr "" msgstr ""
@ -2853,11 +2853,11 @@ msgstr ""
msgid "(click to open/close)" msgid "(click to open/close)"
msgstr "" msgstr ""
#: mod/settings.php:1224 mod/photos.php:1126 mod/photos.php:1456 #: mod/settings.php:1224 mod/photos.php:1128 mod/photos.php:1458
msgid "Show to Groups" msgid "Show to Groups"
msgstr "" msgstr ""
#: mod/settings.php:1225 mod/photos.php:1127 mod/photos.php:1457 #: mod/settings.php:1225 mod/photos.php:1129 mod/photos.php:1459
msgid "Show to Contacts" msgid "Show to Contacts"
msgstr "" msgstr ""
@ -3006,7 +3006,7 @@ msgstr ""
msgid "Items tagged with: %s" msgid "Items tagged with: %s"
msgstr "" msgstr ""
#: mod/search.php:248 mod/contacts.php:841 #: mod/search.php:248 mod/contacts.php:844
#, php-format #, php-format
msgid "Results for: %s" msgid "Results for: %s"
msgstr "" msgstr ""
@ -3015,7 +3015,7 @@ msgstr ""
msgid "No contacts in common." msgid "No contacts in common."
msgstr "" msgstr ""
#: mod/common.php:142 mod/contacts.php:916 #: mod/common.php:142 mod/contacts.php:919
msgid "Common Friends" msgid "Common Friends"
msgstr "" msgstr ""
@ -3023,7 +3023,11 @@ msgstr ""
msgid "Login" msgid "Login"
msgstr "" msgstr ""
#: mod/bookmarklet.php:52 #: mod/bookmarklet.php:34
msgid "Bad Request"
msgstr ""
#: mod/bookmarklet.php:56
msgid "The post was created" msgid "The post was created"
msgstr "" msgstr ""
@ -3173,7 +3177,7 @@ msgstr ""
msgid "Members" msgid "Members"
msgstr "" msgstr ""
#: mod/group.php:246 mod/contacts.php:739 #: mod/group.php:246 mod/contacts.php:742
msgid "All Contacts" msgid "All Contacts"
msgstr "" msgstr ""
@ -3297,46 +3301,42 @@ msgstr ""
msgid "No contacts." msgid "No contacts."
msgstr "" msgstr ""
#: mod/viewcontacts.php:106 mod/contacts.php:639 mod/contacts.php:1052 #: mod/viewcontacts.php:106 mod/contacts.php:640 mod/contacts.php:1055
#, php-format #, php-format
msgid "Visit %s's profile [%s]" msgid "Visit %s's profile [%s]"
msgstr "" msgstr ""
#: mod/unfollow.php:36 #: mod/unfollow.php:38 mod/unfollow.php:88
msgid "Contact wasn't found or can't be unfollowed." msgid "You aren't following this contact."
msgstr "" msgstr ""
#: mod/unfollow.php:49 #: mod/unfollow.php:44 mod/unfollow.php:94
msgid "Contact unfollowed"
msgstr ""
#: mod/unfollow.php:67 mod/dfrn_request.php:654 mod/follow.php:62
msgid "Submit Request"
msgstr ""
#: mod/unfollow.php:76
msgid "You aren't a friend of this contact."
msgstr ""
#: mod/unfollow.php:82
msgid "Unfollowing is currently not supported by your network." msgid "Unfollowing is currently not supported by your network."
msgstr "" msgstr ""
#: mod/unfollow.php:103 mod/contacts.php:601 #: mod/unfollow.php:65
msgid "Contact unfollowed"
msgstr ""
#: mod/unfollow.php:113 mod/contacts.php:607
msgid "Disconnect/Unfollow" msgid "Disconnect/Unfollow"
msgstr "" msgstr ""
#: mod/unfollow.php:116 mod/dfrn_request.php:652 mod/follow.php:157 #: mod/unfollow.php:126 mod/dfrn_request.php:652 mod/follow.php:157
msgid "Your Identity Address:" msgid "Your Identity Address:"
msgstr "" msgstr ""
#: mod/unfollow.php:125 mod/notifications.php:174 mod/notifications.php:263 #: mod/unfollow.php:129 mod/dfrn_request.php:654 mod/follow.php:62
#: mod/admin.php:500 mod/admin.php:510 mod/contacts.php:676 mod/follow.php:166 msgid "Submit Request"
msgstr ""
#: mod/unfollow.php:135 mod/notifications.php:174 mod/notifications.php:258
#: mod/admin.php:500 mod/admin.php:510 mod/contacts.php:677 mod/follow.php:166
msgid "Profile URL" msgid "Profile URL"
msgstr "" msgstr ""
#: mod/unfollow.php:135 mod/contacts.php:888 mod/follow.php:189 #: mod/unfollow.php:145 mod/contacts.php:891 mod/follow.php:189
#: src/Model/Profile.php:878 #: src/Model/Profile.php:891
msgid "Status Messages and Posts" msgid "Status Messages and Posts"
msgstr "" msgstr ""
@ -3370,7 +3370,7 @@ msgstr ""
msgid "Your registration is pending approval by the site owner." msgid "Your registration is pending approval by the site owner."
msgstr "" msgstr ""
#: mod/register.php:191 mod/uimport.php:55 #: mod/register.php:191 mod/uimport.php:37
msgid "" msgid ""
"This site has exceeded the number of allowed daily account registrations. " "This site has exceeded the number of allowed daily account registrations. "
"Please try again tomorrow." "Please try again tomorrow."
@ -3445,7 +3445,7 @@ msgstr ""
msgid "Register" msgid "Register"
msgstr "" msgstr ""
#: mod/register.php:287 mod/uimport.php:70 #: mod/register.php:287 mod/uimport.php:52
msgid "Import" msgid "Import"
msgstr "" msgstr ""
@ -3466,36 +3466,44 @@ msgstr ""
msgid "Invalid request identifier." msgid "Invalid request identifier."
msgstr "" msgstr ""
#: mod/notifications.php:44 mod/notifications.php:183 mod/notifications.php:235 #: mod/notifications.php:44 mod/notifications.php:182 mod/notifications.php:230
#: mod/message.php:114 #: mod/message.php:114
msgid "Discard" msgid "Discard"
msgstr "" msgstr ""
#: mod/notifications.php:57 mod/notifications.php:182 mod/notifications.php:271 #: mod/notifications.php:57 mod/notifications.php:181 mod/notifications.php:266
#: mod/contacts.php:658 mod/contacts.php:850 mod/contacts.php:1113 #: mod/contacts.php:659 mod/contacts.php:853 mod/contacts.php:1116
msgid "Ignore" msgid "Ignore"
msgstr "" msgstr ""
#: mod/notifications.php:93 src/Content/Nav.php:191 #: mod/notifications.php:90 src/Content/Nav.php:191
msgid "Notifications" msgid "Notifications"
msgstr "" msgstr ""
#: mod/notifications.php:101 #: mod/notifications.php:102
msgid "Network Notifications" msgid "Network Notifications"
msgstr "" msgstr ""
#: mod/notifications.php:106 mod/notify.php:81 #: mod/notifications.php:107 mod/notify.php:81
msgid "System Notifications" msgid "System Notifications"
msgstr "" msgstr ""
#: mod/notifications.php:111 #: mod/notifications.php:112
msgid "Personal Notifications" msgid "Personal Notifications"
msgstr "" msgstr ""
#: mod/notifications.php:116 #: mod/notifications.php:117
msgid "Home Notifications" msgid "Home Notifications"
msgstr "" msgstr ""
#: mod/notifications.php:137
msgid "Show unread"
msgstr ""
#: mod/notifications.php:137
msgid "Show all"
msgstr ""
#: mod/notifications.php:148 #: mod/notifications.php:148
msgid "Show Ignored Requests" msgid "Show Ignored Requests"
msgstr "" msgstr ""
@ -3504,7 +3512,7 @@ msgstr ""
msgid "Hide Ignored Requests" msgid "Hide Ignored Requests"
msgstr "" msgstr ""
#: mod/notifications.php:161 mod/notifications.php:243 #: mod/notifications.php:161 mod/notifications.php:238
msgid "Notification type:" msgid "Notification type:"
msgstr "" msgstr ""
@ -3512,85 +3520,77 @@ msgstr ""
msgid "Suggested by:" msgid "Suggested by:"
msgstr "" msgstr ""
#: mod/notifications.php:176 mod/notifications.php:260 mod/contacts.php:666 #: mod/notifications.php:176 mod/notifications.php:255 mod/contacts.php:667
msgid "Hide this contact from others" msgid "Hide this contact from others"
msgstr "" msgstr ""
#: mod/notifications.php:179 mod/notifications.php:269 mod/admin.php:1904 #: mod/notifications.php:178 mod/notifications.php:264 mod/admin.php:1904
msgid "Approve" msgid "Approve"
msgstr "" msgstr ""
#: mod/notifications.php:202 #: mod/notifications.php:198
msgid "Claims to be known to you: " msgid "Claims to be known to you: "
msgstr "" msgstr ""
#: mod/notifications.php:203 #: mod/notifications.php:199
msgid "yes" msgid "yes"
msgstr "" msgstr ""
#: mod/notifications.php:203 #: mod/notifications.php:199
msgid "no" msgid "no"
msgstr "" msgstr ""
#: mod/notifications.php:204 mod/notifications.php:209 #: mod/notifications.php:200 mod/notifications.php:204
msgid "Shall your connection be bidirectional or not?" msgid "Shall your connection be bidirectional or not?"
msgstr "" msgstr ""
#: mod/notifications.php:205 mod/notifications.php:210 #: mod/notifications.php:201 mod/notifications.php:205
#, php-format #, php-format
msgid "" msgid ""
"Accepting %s as a friend allows %s to subscribe to your posts, and you will " "Accepting %s as a friend allows %s to subscribe to your posts, and you will "
"also receive updates from them in your news feed." "also receive updates from them in your news feed."
msgstr "" msgstr ""
#: mod/notifications.php:206 #: mod/notifications.php:202
#, php-format #, php-format
msgid "" msgid ""
"Accepting %s as a subscriber allows them to subscribe to your posts, but you " "Accepting %s as a subscriber allows them to subscribe to your posts, but you "
"will not receive updates from them in your news feed." "will not receive updates from them in your news feed."
msgstr "" msgstr ""
#: mod/notifications.php:211 #: mod/notifications.php:206
#, php-format #, php-format
msgid "" msgid ""
"Accepting %s as a sharer allows them to subscribe to your posts, but you " "Accepting %s as a sharer allows them to subscribe to your posts, but you "
"will not receive updates from them in your news feed." "will not receive updates from them in your news feed."
msgstr "" msgstr ""
#: mod/notifications.php:222 #: mod/notifications.php:217
msgid "Friend" msgid "Friend"
msgstr "" msgstr ""
#: mod/notifications.php:223 #: mod/notifications.php:218
msgid "Sharer" msgid "Sharer"
msgstr "" msgstr ""
#: mod/notifications.php:223 #: mod/notifications.php:218
msgid "Subscriber" msgid "Subscriber"
msgstr "" msgstr ""
#: mod/notifications.php:257 mod/contacts.php:686 mod/follow.php:177 #: mod/notifications.php:252 mod/contacts.php:687 mod/follow.php:177
#: src/Model/Profile.php:781 #: src/Model/Profile.php:794
msgid "Tags:" msgid "Tags:"
msgstr "" msgstr ""
#: mod/notifications.php:266 mod/contacts.php:76 src/Model/Profile.php:520 #: mod/notifications.php:261 mod/contacts.php:81 src/Model/Profile.php:533
msgid "Network:" msgid "Network:"
msgstr "" msgstr ""
#: mod/notifications.php:280 #: mod/notifications.php:274
msgid "No introductions." msgid "No introductions."
msgstr "" msgstr ""
#: mod/notifications.php:318 #: mod/notifications.php:308
msgid "Show unread"
msgstr ""
#: mod/notifications.php:318
msgid "Show all"
msgstr ""
#: mod/notifications.php:323
#, php-format #, php-format
msgid "No more %s notifications." msgid "No more %s notifications."
msgstr "" msgstr ""
@ -3812,7 +3812,7 @@ msgid "On this server the following remote servers are blocked."
msgstr "" msgstr ""
#: mod/friendica.php:130 mod/admin.php:363 mod/admin.php:381 #: mod/friendica.php:130 mod/admin.php:363 mod/admin.php:381
#: mod/dfrn_request.php:345 src/Model/Contact.php:1582 #: mod/dfrn_request.php:345 src/Model/Contact.php:1593
msgid "Blocked domain" msgid "Blocked domain"
msgstr "" msgstr ""
@ -3820,7 +3820,7 @@ msgstr ""
msgid "Reason for the block" msgid "Reason for the block"
msgstr "" msgstr ""
#: mod/display.php:303 mod/cal.php:144 mod/profile.php:175 #: mod/display.php:312 mod/cal.php:144 mod/profile.php:185
msgid "Access to this profile has been restricted." msgid "Access to this profile has been restricted."
msgstr "" msgstr ""
@ -3830,13 +3830,13 @@ msgstr ""
msgid "Invalid request." msgid "Invalid request."
msgstr "" msgstr ""
#: mod/wall_upload.php:195 mod/profile_photo.php:151 mod/photos.php:776 #: mod/wall_upload.php:195 mod/profile_photo.php:151 mod/photos.php:778
#: mod/photos.php:779 mod/photos.php:808 #: mod/photos.php:781 mod/photos.php:810
#, php-format #, php-format
msgid "Image exceeds size limit of %s" msgid "Image exceeds size limit of %s"
msgstr "" msgstr ""
#: mod/wall_upload.php:209 mod/profile_photo.php:160 mod/photos.php:831 #: mod/wall_upload.php:209 mod/profile_photo.php:160 mod/photos.php:833
msgid "Unable to process image." msgid "Unable to process image."
msgstr "" msgstr ""
@ -3845,7 +3845,7 @@ msgstr ""
msgid "Wall Photos" msgid "Wall Photos"
msgstr "" msgstr ""
#: mod/wall_upload.php:248 mod/profile_photo.php:305 mod/photos.php:860 #: mod/wall_upload.php:248 mod/profile_photo.php:305 mod/photos.php:862
msgid "Image upload failed." msgid "Image upload failed."
msgstr "" msgstr ""
@ -4156,79 +4156,91 @@ msgstr ""
msgid "Your password has been changed at %s" msgid "Your password has been changed at %s"
msgstr "" msgstr ""
#: mod/babel.php:22 #: mod/babel.php:24
msgid "Source input" msgid "Source input"
msgstr "" msgstr ""
#: mod/babel.php:28 #: mod/babel.php:30
msgid "BBCode::toPlaintext" msgid "BBCode::toPlaintext"
msgstr "" msgstr ""
#: mod/babel.php:34 #: mod/babel.php:36
msgid "BBCode::convert (raw HTML)" msgid "BBCode::convert (raw HTML)"
msgstr "" msgstr ""
#: mod/babel.php:39 #: mod/babel.php:41
msgid "BBCode::convert" msgid "BBCode::convert"
msgstr "" msgstr ""
#: mod/babel.php:45 #: mod/babel.php:47
msgid "BBCode::convert => HTML::toBBCode" msgid "BBCode::convert => HTML::toBBCode"
msgstr "" msgstr ""
#: mod/babel.php:51 #: mod/babel.php:53
msgid "BBCode::toMarkdown" msgid "BBCode::toMarkdown"
msgstr "" msgstr ""
#: mod/babel.php:57 #: mod/babel.php:59
msgid "BBCode::toMarkdown => Markdown::convert" msgid "BBCode::toMarkdown => Markdown::convert"
msgstr "" msgstr ""
#: mod/babel.php:63 #: mod/babel.php:65
msgid "BBCode::toMarkdown => Markdown::toBBCode" msgid "BBCode::toMarkdown => Markdown::toBBCode"
msgstr "" msgstr ""
#: mod/babel.php:69 #: mod/babel.php:71
msgid "BBCode::toMarkdown => Markdown::convert => HTML::toBBCode" msgid "BBCode::toMarkdown => Markdown::convert => HTML::toBBCode"
msgstr "" msgstr ""
#: mod/babel.php:76 #: mod/babel.php:78
msgid "Source input \\x28Diaspora format\\x29" msgid "Source input (Diaspora format)"
msgstr "" msgstr ""
#: mod/babel.php:82 #: mod/babel.php:84
msgid "Markdown::toBBCode" msgid "Markdown::convert (raw HTML)"
msgstr "" msgstr ""
#: mod/babel.php:89 #: mod/babel.php:89
msgid "Markdown::convert"
msgstr ""
#: mod/babel.php:95
msgid "Markdown::toBBCode"
msgstr ""
#: mod/babel.php:102
msgid "Raw HTML input" msgid "Raw HTML input"
msgstr "" msgstr ""
#: mod/babel.php:94 #: mod/babel.php:107
msgid "HTML Input" msgid "HTML Input"
msgstr "" msgstr ""
#: mod/babel.php:100 #: mod/babel.php:113
msgid "HTML::toBBCode" msgid "HTML::toBBCode"
msgstr "" msgstr ""
#: mod/babel.php:106 #: mod/babel.php:119
msgid "HTML::toMarkdown"
msgstr ""
#: mod/babel.php:125
msgid "HTML::toPlaintext" msgid "HTML::toPlaintext"
msgstr "" msgstr ""
#: mod/babel.php:114 #: mod/babel.php:133
msgid "Source text" msgid "Source text"
msgstr "" msgstr ""
#: mod/babel.php:115 #: mod/babel.php:134
msgid "BBCode" msgid "BBCode"
msgstr "" msgstr ""
#: mod/babel.php:116 #: mod/babel.php:135
msgid "Markdown" msgid "Markdown"
msgstr "" msgstr ""
#: mod/babel.php:117 #: mod/babel.php:136
msgid "HTML" msgid "HTML"
msgstr "" msgstr ""
@ -4488,13 +4500,13 @@ msgstr ""
msgid "select none" msgid "select none"
msgstr "" msgstr ""
#: mod/admin.php:494 mod/admin.php:1907 mod/contacts.php:657 #: mod/admin.php:494 mod/admin.php:1907 mod/contacts.php:658
#: mod/contacts.php:849 mod/contacts.php:1105 #: mod/contacts.php:852 mod/contacts.php:1108
msgid "Block" msgid "Block"
msgstr "" msgstr ""
#: mod/admin.php:495 mod/admin.php:1909 mod/contacts.php:657 #: mod/admin.php:495 mod/admin.php:1909 mod/contacts.php:658
#: mod/contacts.php:849 mod/contacts.php:1105 #: mod/contacts.php:852 mod/contacts.php:1108
msgid "Unblock" msgid "Unblock"
msgstr "" msgstr ""
@ -4675,9 +4687,9 @@ msgstr ""
#: mod/admin.php:876 #: mod/admin.php:876
#, php-format #, php-format
msgid "" msgid ""
"<a href=\"%s\">%s</a> is not reachable on your system. This is a servere " "<a href=\"%s\">%s</a> is not reachable on your system. This is a severe "
"configuration issue that prevents the communication.. See <a href=\"%s\">the " "configuration issue that prevents server to server communication. See <a "
"installation page</a> for help." "href=\"%s\">the installation page</a> for help."
msgstr "" msgstr ""
#: mod/admin.php:882 #: mod/admin.php:882
@ -4757,7 +4769,7 @@ msgid "Public postings from local users and the federated network"
msgstr "" msgstr ""
#: mod/admin.php:1353 mod/admin.php:1520 mod/admin.php:1530 #: mod/admin.php:1353 mod/admin.php:1520 mod/admin.php:1530
#: mod/contacts.php:577 #: mod/contacts.php:583
msgid "Disabled" msgid "Disabled"
msgstr "" msgstr ""
@ -4837,8 +4849,8 @@ msgstr ""
msgid "Policies" msgid "Policies"
msgstr "" msgstr ""
#: mod/admin.php:1431 mod/contacts.php:926 mod/events.php:535 #: mod/admin.php:1431 mod/contacts.php:929 mod/events.php:562
#: src/Model/Profile.php:852 #: src/Model/Profile.php:865
msgid "Advanced" msgid "Advanced"
msgstr "" msgstr ""
@ -5241,14 +5253,14 @@ msgid ""
msgstr "" msgstr ""
#: mod/admin.php:1481 #: mod/admin.php:1481
msgid "Only import OStatus threads from our contacts" msgid "Only import OStatus/ActivityPub threads from our contacts"
msgstr "" msgstr ""
#: mod/admin.php:1481 #: mod/admin.php:1481
msgid "" msgid ""
"Normally we import every content from our OStatus contacts. With this option " "Normally we import every content from our OStatus and ActivityPub contacts. "
"we only store threads that are started by a contact that is known on our " "With this option we only store threads that are started by a contact that is "
"system." "known on our system."
msgstr "" msgstr ""
#: mod/admin.php:1482 #: mod/admin.php:1482
@ -6127,11 +6139,11 @@ msgstr ""
msgid "Invalid profile URL." msgid "Invalid profile URL."
msgstr "" msgstr ""
#: mod/dfrn_request.php:339 src/Model/Contact.php:1577 #: mod/dfrn_request.php:339 src/Model/Contact.php:1588
msgid "Disallowed profile URL." msgid "Disallowed profile URL."
msgstr "" msgstr ""
#: mod/dfrn_request.php:412 mod/contacts.php:235 #: mod/dfrn_request.php:412 mod/contacts.php:241
msgid "Failed to update contact record." msgid "Failed to update contact record."
msgstr "" msgstr ""
@ -6357,32 +6369,36 @@ msgstr ""
msgid "Help:" msgid "Help:"
msgstr "" msgstr ""
#: mod/uimport.php:72 #: mod/uimport.php:28
msgid "User imports on closed servers can only be done by an administrator."
msgstr ""
#: mod/uimport.php:54
msgid "Move account" msgid "Move account"
msgstr "" msgstr ""
#: mod/uimport.php:73 #: mod/uimport.php:55
msgid "You can import an account from another Friendica server." msgid "You can import an account from another Friendica server."
msgstr "" msgstr ""
#: mod/uimport.php:74 #: mod/uimport.php:56
msgid "" msgid ""
"You need to export your account from the old server and upload it here. We " "You need to export your account from the old server and upload it here. We "
"will recreate your old account here with all your contacts. We will try also " "will recreate your old account here with all your contacts. We will try also "
"to inform your friends that you moved here." "to inform your friends that you moved here."
msgstr "" msgstr ""
#: mod/uimport.php:75 #: mod/uimport.php:57
msgid "" msgid ""
"This feature is experimental. We can't import contacts from the OStatus " "This feature is experimental. We can't import contacts from the OStatus "
"network (GNU Social/Statusnet) or from Diaspora" "network (GNU Social/Statusnet) or from Diaspora"
msgstr "" msgstr ""
#: mod/uimport.php:76 #: mod/uimport.php:58
msgid "Account file" msgid "Account file"
msgstr "" msgstr ""
#: mod/uimport.php:76 #: mod/uimport.php:58
msgid "" msgid ""
"To export your account, go to \"Settings->Export your personal data\" and " "To export your account, go to \"Settings->Export your personal data\" and "
"select \"Export account\"" "select \"Export account\""
@ -6404,34 +6420,34 @@ msgstr ""
msgid "All Contacts (with secure profile access)" msgid "All Contacts (with secure profile access)"
msgstr "" msgstr ""
#: mod/cal.php:277 mod/events.php:391 #: mod/cal.php:277 mod/events.php:392
msgid "View" msgid "View"
msgstr "" msgstr ""
#: mod/cal.php:278 mod/events.php:393 #: mod/cal.php:278 mod/events.php:394
msgid "Previous" msgid "Previous"
msgstr "" msgstr ""
#: mod/cal.php:282 mod/events.php:399 src/Model/Event.php:421 #: mod/cal.php:282 mod/events.php:400 src/Model/Event.php:422
msgid "today" msgid "today"
msgstr "" msgstr ""
#: mod/cal.php:283 mod/events.php:400 src/Util/Temporal.php:304 #: mod/cal.php:283 mod/events.php:401 src/Util/Temporal.php:304
#: src/Model/Event.php:422 #: src/Model/Event.php:423
msgid "month" msgid "month"
msgstr "" msgstr ""
#: mod/cal.php:284 mod/events.php:401 src/Util/Temporal.php:305 #: mod/cal.php:284 mod/events.php:402 src/Util/Temporal.php:305
#: src/Model/Event.php:423 #: src/Model/Event.php:424
msgid "week" msgid "week"
msgstr "" msgstr ""
#: mod/cal.php:285 mod/events.php:402 src/Util/Temporal.php:306 #: mod/cal.php:285 mod/events.php:403 src/Util/Temporal.php:306
#: src/Model/Event.php:424 #: src/Model/Event.php:425
msgid "day" msgid "day"
msgstr "" msgstr ""
#: mod/cal.php:286 mod/events.php:403 #: mod/cal.php:286 mod/events.php:404
msgid "list" msgid "list"
msgstr "" msgstr ""
@ -6464,19 +6480,19 @@ msgstr ""
msgid "Please login." msgid "Please login."
msgstr "" msgstr ""
#: mod/editpost.php:26 mod/editpost.php:36 #: mod/editpost.php:27 mod/editpost.php:42
msgid "Item not found" msgid "Item not found"
msgstr "" msgstr ""
#: mod/editpost.php:43 #: mod/editpost.php:49
msgid "Edit post" msgid "Edit post"
msgstr "" msgstr ""
#: mod/editpost.php:125 src/Core/ACL.php:304 #: mod/editpost.php:131 src/Core/ACL.php:304
msgid "CC: email addresses" msgid "CC: email addresses"
msgstr "" msgstr ""
#: mod/editpost.php:132 src/Core/ACL.php:305 #: mod/editpost.php:138 src/Core/ACL.php:305
msgid "Example: bob@example.com, mary@example.com" msgid "Example: bob@example.com, mary@example.com"
msgstr "" msgstr ""
@ -6513,21 +6529,21 @@ msgstr ""
msgid "System down for maintenance" msgid "System down for maintenance"
msgstr "" msgstr ""
#: mod/profile.php:38 src/Model/Profile.php:115 #: mod/profile.php:39 src/Model/Profile.php:128
msgid "Requested profile is not available." msgid "Requested profile is not available."
msgstr "" msgstr ""
#: mod/profile.php:79 mod/profile.php:82 src/Protocol/OStatus.php:1275 #: mod/profile.php:89 mod/profile.php:92 src/Protocol/OStatus.php:1285
#, php-format #, php-format
msgid "%s's timeline" msgid "%s's timeline"
msgstr "" msgstr ""
#: mod/profile.php:80 src/Protocol/OStatus.php:1276 #: mod/profile.php:90 src/Protocol/OStatus.php:1286
#, php-format #, php-format
msgid "%s's posts" msgid "%s's posts"
msgstr "" msgstr ""
#: mod/profile.php:81 src/Protocol/OStatus.php:1277 #: mod/profile.php:91 src/Protocol/OStatus.php:1287
#, php-format #, php-format
msgid "%s's comments" msgid "%s's comments"
msgstr "" msgstr ""
@ -6536,433 +6552,433 @@ msgstr ""
msgid "No friends to display." msgid "No friends to display."
msgstr "" msgstr ""
#: mod/contacts.php:162 #: mod/contacts.php:168
#, php-format #, php-format
msgid "%d contact edited." msgid "%d contact edited."
msgid_plural "%d contacts edited." msgid_plural "%d contacts edited."
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#: mod/contacts.php:189 mod/contacts.php:395 #: mod/contacts.php:195 mod/contacts.php:401
msgid "Could not access contact record." msgid "Could not access contact record."
msgstr "" msgstr ""
#: mod/contacts.php:199 #: mod/contacts.php:205
msgid "Could not locate selected profile." msgid "Could not locate selected profile."
msgstr "" msgstr ""
#: mod/contacts.php:233 #: mod/contacts.php:239
msgid "Contact updated." msgid "Contact updated."
msgstr "" msgstr ""
#: mod/contacts.php:416 #: mod/contacts.php:422
msgid "Contact has been blocked" msgid "Contact has been blocked"
msgstr "" msgstr ""
#: mod/contacts.php:416 #: mod/contacts.php:422
msgid "Contact has been unblocked" msgid "Contact has been unblocked"
msgstr "" msgstr ""
#: mod/contacts.php:426 #: mod/contacts.php:432
msgid "Contact has been ignored" msgid "Contact has been ignored"
msgstr "" msgstr ""
#: mod/contacts.php:426 #: mod/contacts.php:432
msgid "Contact has been unignored" msgid "Contact has been unignored"
msgstr "" msgstr ""
#: mod/contacts.php:436 #: mod/contacts.php:442
msgid "Contact has been archived" msgid "Contact has been archived"
msgstr "" msgstr ""
#: mod/contacts.php:436 #: mod/contacts.php:442
msgid "Contact has been unarchived" msgid "Contact has been unarchived"
msgstr "" msgstr ""
#: mod/contacts.php:460 #: mod/contacts.php:466
msgid "Drop contact" msgid "Drop contact"
msgstr "" msgstr ""
#: mod/contacts.php:463 mod/contacts.php:845 #: mod/contacts.php:469 mod/contacts.php:848
msgid "Do you really want to delete this contact?" msgid "Do you really want to delete this contact?"
msgstr "" msgstr ""
#: mod/contacts.php:481 #: mod/contacts.php:487
msgid "Contact has been removed." msgid "Contact has been removed."
msgstr "" msgstr ""
#: mod/contacts.php:518 #: mod/contacts.php:524
#, php-format #, php-format
msgid "You are mutual friends with %s" msgid "You are mutual friends with %s"
msgstr "" msgstr ""
#: mod/contacts.php:523 #: mod/contacts.php:529
#, php-format #, php-format
msgid "You are sharing with %s" msgid "You are sharing with %s"
msgstr "" msgstr ""
#: mod/contacts.php:528 #: mod/contacts.php:534
#, php-format #, php-format
msgid "%s is sharing with you" msgid "%s is sharing with you"
msgstr "" msgstr ""
#: mod/contacts.php:552 #: mod/contacts.php:558
msgid "Private communications are not available for this contact." msgid "Private communications are not available for this contact."
msgstr "" msgstr ""
#: mod/contacts.php:554 #: mod/contacts.php:560
msgid "Never" msgid "Never"
msgstr "" msgstr ""
#: mod/contacts.php:557 #: mod/contacts.php:563
msgid "(Update was successful)" msgid "(Update was successful)"
msgstr "" msgstr ""
#: mod/contacts.php:557 #: mod/contacts.php:563
msgid "(Update was not successful)" msgid "(Update was not successful)"
msgstr "" msgstr ""
#: mod/contacts.php:559 mod/contacts.php:1086 #: mod/contacts.php:565 mod/contacts.php:1089
msgid "Suggest friends" msgid "Suggest friends"
msgstr "" msgstr ""
#: mod/contacts.php:563 #: mod/contacts.php:569
#, php-format #, php-format
msgid "Network type: %s" msgid "Network type: %s"
msgstr "" msgstr ""
#: mod/contacts.php:568 #: mod/contacts.php:574
msgid "Communications lost with this contact!" msgid "Communications lost with this contact!"
msgstr "" msgstr ""
#: mod/contacts.php:574 #: mod/contacts.php:580
msgid "Fetch further information for feeds" msgid "Fetch further information for feeds"
msgstr "" msgstr ""
#: mod/contacts.php:576 #: mod/contacts.php:582
msgid "" msgid ""
"Fetch information like preview pictures, title and teaser from the feed " "Fetch information like preview pictures, title and teaser from the feed "
"item. You can activate this if the feed doesn't contain much text. Keywords " "item. You can activate this if the feed doesn't contain much text. Keywords "
"are taken from the meta header in the feed item and are posted as hash tags." "are taken from the meta header in the feed item and are posted as hash tags."
msgstr "" msgstr ""
#: mod/contacts.php:578 #: mod/contacts.php:584
msgid "Fetch information" msgid "Fetch information"
msgstr "" msgstr ""
#: mod/contacts.php:579 #: mod/contacts.php:585
msgid "Fetch keywords" msgid "Fetch keywords"
msgstr "" msgstr ""
#: mod/contacts.php:580 #: mod/contacts.php:586
msgid "Fetch information and keywords" msgid "Fetch information and keywords"
msgstr "" msgstr ""
#: mod/contacts.php:617 #: mod/contacts.php:618
msgid "Profile Visibility" msgid "Profile Visibility"
msgstr "" msgstr ""
#: mod/contacts.php:618 #: mod/contacts.php:619
msgid "Contact Information / Notes" msgid "Contact Information / Notes"
msgstr "" msgstr ""
#: mod/contacts.php:619 #: mod/contacts.php:620
msgid "Contact Settings" msgid "Contact Settings"
msgstr "" msgstr ""
#: mod/contacts.php:628 #: mod/contacts.php:629
msgid "Contact" msgid "Contact"
msgstr "" msgstr ""
#: mod/contacts.php:632 #: mod/contacts.php:633
#, php-format #, php-format
msgid "" msgid ""
"Please choose the profile you would like to display to %s when viewing your " "Please choose the profile you would like to display to %s when viewing your "
"profile securely." "profile securely."
msgstr "" msgstr ""
#: mod/contacts.php:634 #: mod/contacts.php:635
msgid "Their personal note" msgid "Their personal note"
msgstr "" msgstr ""
#: mod/contacts.php:636 #: mod/contacts.php:637
msgid "Edit contact notes" msgid "Edit contact notes"
msgstr "" msgstr ""
#: mod/contacts.php:640 #: mod/contacts.php:641
msgid "Block/Unblock contact" msgid "Block/Unblock contact"
msgstr "" msgstr ""
#: mod/contacts.php:641 #: mod/contacts.php:642
msgid "Ignore contact" msgid "Ignore contact"
msgstr "" msgstr ""
#: mod/contacts.php:642 #: mod/contacts.php:643
msgid "Repair URL settings" msgid "Repair URL settings"
msgstr "" msgstr ""
#: mod/contacts.php:643 #: mod/contacts.php:644
msgid "View conversations" msgid "View conversations"
msgstr "" msgstr ""
#: mod/contacts.php:648 #: mod/contacts.php:649
msgid "Last update:" msgid "Last update:"
msgstr "" msgstr ""
#: mod/contacts.php:650 #: mod/contacts.php:651
msgid "Update public posts" msgid "Update public posts"
msgstr "" msgstr ""
#: mod/contacts.php:652 mod/contacts.php:1096 #: mod/contacts.php:653 mod/contacts.php:1099
msgid "Update now" msgid "Update now"
msgstr "" msgstr ""
#: mod/contacts.php:658 mod/contacts.php:850 mod/contacts.php:1113 #: mod/contacts.php:659 mod/contacts.php:853 mod/contacts.php:1116
msgid "Unignore" msgid "Unignore"
msgstr "" msgstr ""
#: mod/contacts.php:662 #: mod/contacts.php:663
msgid "Currently blocked" msgid "Currently blocked"
msgstr "" msgstr ""
#: mod/contacts.php:663 #: mod/contacts.php:664
msgid "Currently ignored" msgid "Currently ignored"
msgstr "" msgstr ""
#: mod/contacts.php:664 #: mod/contacts.php:665
msgid "Currently archived" msgid "Currently archived"
msgstr "" msgstr ""
#: mod/contacts.php:665 #: mod/contacts.php:666
msgid "Awaiting connection acknowledge" msgid "Awaiting connection acknowledge"
msgstr "" msgstr ""
#: mod/contacts.php:666 #: mod/contacts.php:667
msgid "" msgid ""
"Replies/likes to your public posts <strong>may</strong> still be visible" "Replies/likes to your public posts <strong>may</strong> still be visible"
msgstr "" msgstr ""
#: mod/contacts.php:667 #: mod/contacts.php:668
msgid "Notification for new posts" msgid "Notification for new posts"
msgstr "" msgstr ""
#: mod/contacts.php:667 #: mod/contacts.php:668
msgid "Send a notification of every new post of this contact" msgid "Send a notification of every new post of this contact"
msgstr "" msgstr ""
#: mod/contacts.php:670 #: mod/contacts.php:671
msgid "Blacklisted keywords" msgid "Blacklisted keywords"
msgstr "" msgstr ""
#: mod/contacts.php:670 #: mod/contacts.php:671
msgid "" msgid ""
"Comma separated list of keywords that should not be converted to hashtags, " "Comma separated list of keywords that should not be converted to hashtags, "
"when \"Fetch information and keywords\" is selected" "when \"Fetch information and keywords\" is selected"
msgstr "" msgstr ""
#: mod/contacts.php:682 src/Model/Profile.php:424 #: mod/contacts.php:683 src/Model/Profile.php:437
msgid "XMPP:" msgid "XMPP:"
msgstr "" msgstr ""
#: mod/contacts.php:687 #: mod/contacts.php:688
msgid "Actions" msgid "Actions"
msgstr "" msgstr ""
#: mod/contacts.php:731 #: mod/contacts.php:734
msgid "Suggestions" msgid "Suggestions"
msgstr "" msgstr ""
#: mod/contacts.php:734 #: mod/contacts.php:737
msgid "Suggest potential friends" msgid "Suggest potential friends"
msgstr "" msgstr ""
#: mod/contacts.php:742 #: mod/contacts.php:745
msgid "Show all contacts" msgid "Show all contacts"
msgstr "" msgstr ""
#: mod/contacts.php:747 #: mod/contacts.php:750
msgid "Unblocked" msgid "Unblocked"
msgstr "" msgstr ""
#: mod/contacts.php:750 #: mod/contacts.php:753
msgid "Only show unblocked contacts" msgid "Only show unblocked contacts"
msgstr "" msgstr ""
#: mod/contacts.php:755 #: mod/contacts.php:758
msgid "Blocked" msgid "Blocked"
msgstr "" msgstr ""
#: mod/contacts.php:758 #: mod/contacts.php:761
msgid "Only show blocked contacts" msgid "Only show blocked contacts"
msgstr "" msgstr ""
#: mod/contacts.php:763 #: mod/contacts.php:766
msgid "Ignored" msgid "Ignored"
msgstr "" msgstr ""
#: mod/contacts.php:766 #: mod/contacts.php:769
msgid "Only show ignored contacts" msgid "Only show ignored contacts"
msgstr "" msgstr ""
#: mod/contacts.php:771 #: mod/contacts.php:774
msgid "Archived" msgid "Archived"
msgstr "" msgstr ""
#: mod/contacts.php:774 #: mod/contacts.php:777
msgid "Only show archived contacts" msgid "Only show archived contacts"
msgstr "" msgstr ""
#: mod/contacts.php:779 #: mod/contacts.php:782
msgid "Hidden" msgid "Hidden"
msgstr "" msgstr ""
#: mod/contacts.php:782 #: mod/contacts.php:785
msgid "Only show hidden contacts" msgid "Only show hidden contacts"
msgstr "" msgstr ""
#: mod/contacts.php:840 #: mod/contacts.php:843
msgid "Search your contacts" msgid "Search your contacts"
msgstr "" msgstr ""
#: mod/contacts.php:851 mod/contacts.php:1122 #: mod/contacts.php:854 mod/contacts.php:1125
msgid "Archive" msgid "Archive"
msgstr "" msgstr ""
#: mod/contacts.php:851 mod/contacts.php:1122 #: mod/contacts.php:854 mod/contacts.php:1125
msgid "Unarchive" msgid "Unarchive"
msgstr "" msgstr ""
#: mod/contacts.php:854 #: mod/contacts.php:857
msgid "Batch Actions" msgid "Batch Actions"
msgstr "" msgstr ""
#: mod/contacts.php:880 #: mod/contacts.php:883
msgid "Conversations started by this contact" msgid "Conversations started by this contact"
msgstr "" msgstr ""
#: mod/contacts.php:885 #: mod/contacts.php:888
msgid "Posts and Comments" msgid "Posts and Comments"
msgstr "" msgstr ""
#: mod/contacts.php:896 src/Model/Profile.php:886 #: mod/contacts.php:899 src/Model/Profile.php:899
msgid "Profile Details" msgid "Profile Details"
msgstr "" msgstr ""
#: mod/contacts.php:908 #: mod/contacts.php:911
msgid "View all contacts" msgid "View all contacts"
msgstr "" msgstr ""
#: mod/contacts.php:919 #: mod/contacts.php:922
msgid "View all common friends" msgid "View all common friends"
msgstr "" msgstr ""
#: mod/contacts.php:929 #: mod/contacts.php:932
msgid "Advanced Contact Settings" msgid "Advanced Contact Settings"
msgstr "" msgstr ""
#: mod/contacts.php:1019 #: mod/contacts.php:1022
msgid "Mutual Friendship" msgid "Mutual Friendship"
msgstr "" msgstr ""
#: mod/contacts.php:1024 #: mod/contacts.php:1027
msgid "is a fan of yours" msgid "is a fan of yours"
msgstr "" msgstr ""
#: mod/contacts.php:1029 #: mod/contacts.php:1032
msgid "you are a fan of" msgid "you are a fan of"
msgstr "" msgstr ""
#: mod/contacts.php:1046 mod/photos.php:1494 mod/photos.php:1533 #: mod/contacts.php:1049 mod/photos.php:1496 mod/photos.php:1535
#: mod/photos.php:1593 src/Object/Post.php:792 #: mod/photos.php:1595 src/Object/Post.php:792
msgid "This is you" msgid "This is you"
msgstr "" msgstr ""
#: mod/contacts.php:1053 #: mod/contacts.php:1056
msgid "Edit contact" msgid "Edit contact"
msgstr "" msgstr ""
#: mod/contacts.php:1107 #: mod/contacts.php:1110
msgid "Toggle Blocked status" msgid "Toggle Blocked status"
msgstr "" msgstr ""
#: mod/contacts.php:1115 #: mod/contacts.php:1118
msgid "Toggle Ignored status" msgid "Toggle Ignored status"
msgstr "" msgstr ""
#: mod/contacts.php:1124 #: mod/contacts.php:1127
msgid "Toggle Archive status" msgid "Toggle Archive status"
msgstr "" msgstr ""
#: mod/contacts.php:1132 #: mod/contacts.php:1135
msgid "Delete contact" msgid "Delete contact"
msgstr "" msgstr ""
#: mod/events.php:103 mod/events.php:105 #: mod/events.php:105 mod/events.php:107
msgid "Event can not end before it has started." msgid "Event can not end before it has started."
msgstr "" msgstr ""
#: mod/events.php:112 mod/events.php:114 #: mod/events.php:114 mod/events.php:116
msgid "Event title and start time are required." msgid "Event title and start time are required."
msgstr "" msgstr ""
#: mod/events.php:392 #: mod/events.php:393
msgid "Create New Event" msgid "Create New Event"
msgstr "" msgstr ""
#: mod/events.php:509 #: mod/events.php:516
msgid "Event details" msgid "Event details"
msgstr "" msgstr ""
#: mod/events.php:510 #: mod/events.php:517
msgid "Starting date and Title are required." msgid "Starting date and Title are required."
msgstr "" msgstr ""
#: mod/events.php:511 mod/events.php:512 #: mod/events.php:518 mod/events.php:523
msgid "Event Starts:" msgid "Event Starts:"
msgstr "" msgstr ""
#: mod/events.php:511 mod/events.php:523 mod/profiles.php:607 #: mod/events.php:518 mod/events.php:550 mod/profiles.php:607
msgid "Required" msgid "Required"
msgstr "" msgstr ""
#: mod/events.php:513 mod/events.php:529 #: mod/events.php:531 mod/events.php:556
msgid "Finish date/time is not known or not relevant" msgid "Finish date/time is not known or not relevant"
msgstr "" msgstr ""
#: mod/events.php:515 mod/events.php:516 #: mod/events.php:533 mod/events.php:538
msgid "Event Finishes:" msgid "Event Finishes:"
msgstr "" msgstr ""
#: mod/events.php:517 mod/events.php:530 #: mod/events.php:544 mod/events.php:557
msgid "Adjust for viewer timezone" msgid "Adjust for viewer timezone"
msgstr "" msgstr ""
#: mod/events.php:519 #: mod/events.php:546
msgid "Description:" msgid "Description:"
msgstr "" msgstr ""
#: mod/events.php:523 mod/events.php:525 #: mod/events.php:550 mod/events.php:552
msgid "Title:" msgid "Title:"
msgstr "" msgstr ""
#: mod/events.php:526 mod/events.php:527 #: mod/events.php:553 mod/events.php:554
msgid "Share this event" msgid "Share this event"
msgstr "" msgstr ""
#: mod/events.php:534 src/Model/Profile.php:851 #: mod/events.php:561 src/Model/Profile.php:864
msgid "Basic" msgid "Basic"
msgstr "" msgstr ""
#: mod/events.php:536 mod/photos.php:1112 mod/photos.php:1448 #: mod/events.php:563 mod/photos.php:1114 mod/photos.php:1450
#: src/Core/ACL.php:307 #: src/Core/ACL.php:307
msgid "Permissions" msgid "Permissions"
msgstr "" msgstr ""
#: mod/events.php:555 #: mod/events.php:579
msgid "Failed to remove event" msgid "Failed to remove event"
msgstr "" msgstr ""
#: mod/events.php:557 #: mod/events.php:581
msgid "Event removed" msgid "Event removed"
msgstr "" msgstr ""
@ -6987,8 +7003,8 @@ msgid "The network type couldn't be detected. Contact can't be added."
msgstr "" msgstr ""
#: mod/fbrowser.php:44 mod/fbrowser.php:69 mod/photos.php:198 #: mod/fbrowser.php:44 mod/fbrowser.php:69 mod/photos.php:198
#: mod/photos.php:1076 mod/photos.php:1169 mod/photos.php:1186 #: mod/photos.php:1078 mod/photos.php:1171 mod/photos.php:1188
#: mod/photos.php:1652 mod/photos.php:1667 src/Model/Photo.php:243 #: mod/photos.php:1654 mod/photos.php:1669 src/Model/Photo.php:243
#: src/Model/Photo.php:252 #: src/Model/Photo.php:252
msgid "Contact Photos" msgid "Contact Photos"
msgstr "" msgstr ""
@ -7059,7 +7075,7 @@ msgid ""
"not reflect the opinions of this nodes users." "not reflect the opinions of this nodes users."
msgstr "" msgstr ""
#: mod/localtime.php:19 src/Model/Event.php:34 src/Model/Event.php:829 #: mod/localtime.php:19 src/Model/Event.php:35 src/Model/Event.php:836
msgid "l F d, Y \\@ g:i A" msgid "l F d, Y \\@ g:i A"
msgstr "" msgstr ""
@ -7092,23 +7108,23 @@ msgstr ""
msgid "Please select your timezone:" msgid "Please select your timezone:"
msgstr "" msgstr ""
#: mod/poke.php:188 #: mod/poke.php:187
msgid "Poke/Prod" msgid "Poke/Prod"
msgstr "" msgstr ""
#: mod/poke.php:189 #: mod/poke.php:188
msgid "poke, prod or do other things to somebody" msgid "poke, prod or do other things to somebody"
msgstr "" msgstr ""
#: mod/poke.php:190 #: mod/poke.php:189
msgid "Recipient" msgid "Recipient"
msgstr "" msgstr ""
#: mod/poke.php:191 #: mod/poke.php:190
msgid "Choose what you wish to do to recipient" msgid "Choose what you wish to do to recipient"
msgstr "" msgstr ""
#: mod/poke.php:194 #: mod/poke.php:193
msgid "Make this post private" msgid "Make this post private"
msgstr "" msgstr ""
@ -7216,7 +7232,7 @@ msgid ""
"important, please visit http://friendi.ca" "important, please visit http://friendi.ca"
msgstr "" msgstr ""
#: mod/notes.php:42 src/Model/Profile.php:933 #: mod/notes.php:42 src/Model/Profile.php:946
msgid "Personal Notes" msgid "Personal Notes"
msgstr "" msgstr ""
@ -7320,7 +7336,7 @@ msgstr ""
msgid "View all profiles" msgid "View all profiles"
msgstr "" msgstr ""
#: mod/profiles.php:582 mod/profiles.php:677 src/Model/Profile.php:393 #: mod/profiles.php:582 mod/profiles.php:677 src/Model/Profile.php:406
msgid "Edit visibility" msgid "Edit visibility"
msgstr "" msgstr ""
@ -7372,7 +7388,7 @@ msgstr ""
msgid "<span class=\"heart\">&hearts;</span> Marital Status:" msgid "<span class=\"heart\">&hearts;</span> Marital Status:"
msgstr "" msgstr ""
#: mod/profiles.php:601 src/Model/Profile.php:769 #: mod/profiles.php:601 src/Model/Profile.php:782
msgid "Sexual Preference:" msgid "Sexual Preference:"
msgstr "" msgstr ""
@ -7452,11 +7468,11 @@ msgstr ""
msgid "Homepage URL:" msgid "Homepage URL:"
msgstr "" msgstr ""
#: mod/profiles.php:628 src/Model/Profile.php:777 #: mod/profiles.php:628 src/Model/Profile.php:790
msgid "Hometown:" msgid "Hometown:"
msgstr "" msgstr ""
#: mod/profiles.php:629 src/Model/Profile.php:785 #: mod/profiles.php:629 src/Model/Profile.php:798
msgid "Political Views:" msgid "Political Views:"
msgstr "" msgstr ""
@ -7480,11 +7496,11 @@ msgstr ""
msgid "(Used for searching profiles, never shown to others)" msgid "(Used for searching profiles, never shown to others)"
msgstr "" msgstr ""
#: mod/profiles.php:633 src/Model/Profile.php:801 #: mod/profiles.php:633 src/Model/Profile.php:814
msgid "Likes:" msgid "Likes:"
msgstr "" msgstr ""
#: mod/profiles.php:634 src/Model/Profile.php:805 #: mod/profiles.php:634 src/Model/Profile.php:818
msgid "Dislikes:" msgid "Dislikes:"
msgstr "" msgstr ""
@ -7524,11 +7540,11 @@ msgstr ""
msgid "Contact information and Social Networks" msgid "Contact information and Social Networks"
msgstr "" msgstr ""
#: mod/profiles.php:674 src/Model/Profile.php:389 #: mod/profiles.php:674 src/Model/Profile.php:402
msgid "Profile Image" msgid "Profile Image"
msgstr "" msgstr ""
#: mod/profiles.php:676 src/Model/Profile.php:392 #: mod/profiles.php:676 src/Model/Profile.php:405
msgid "visible to everybody" msgid "visible to everybody"
msgstr "" msgstr ""
@ -7536,23 +7552,23 @@ msgstr ""
msgid "Edit/Manage Profiles" msgid "Edit/Manage Profiles"
msgstr "" msgstr ""
#: mod/profiles.php:684 src/Model/Profile.php:379 src/Model/Profile.php:401 #: mod/profiles.php:684 src/Model/Profile.php:392 src/Model/Profile.php:414
msgid "Change profile photo" msgid "Change profile photo"
msgstr "" msgstr ""
#: mod/profiles.php:685 src/Model/Profile.php:380 #: mod/profiles.php:685 src/Model/Profile.php:393
msgid "Create New Profile" msgid "Create New Profile"
msgstr "" msgstr ""
#: mod/photos.php:112 src/Model/Profile.php:894 #: mod/photos.php:112 src/Model/Profile.php:907
msgid "Photo Albums" msgid "Photo Albums"
msgstr "" msgstr ""
#: mod/photos.php:113 mod/photos.php:1708 #: mod/photos.php:113 mod/photos.php:1710
msgid "Recent Photos" msgid "Recent Photos"
msgstr "" msgstr ""
#: mod/photos.php:116 mod/photos.php:1230 mod/photos.php:1710 #: mod/photos.php:116 mod/photos.php:1232 mod/photos.php:1712
msgid "Upload New Photos" msgid "Upload New Photos"
msgstr "" msgstr ""
@ -7564,7 +7580,7 @@ msgstr ""
msgid "Album not found." msgid "Album not found."
msgstr "" msgstr ""
#: mod/photos.php:239 mod/photos.php:252 mod/photos.php:1181 #: mod/photos.php:239 mod/photos.php:252 mod/photos.php:1183
msgid "Delete Album" msgid "Delete Album"
msgstr "" msgstr ""
@ -7572,7 +7588,7 @@ msgstr ""
msgid "Do you really want to delete this photo album and all its photos?" msgid "Do you really want to delete this photo album and all its photos?"
msgstr "" msgstr ""
#: mod/photos.php:312 mod/photos.php:324 mod/photos.php:1453 #: mod/photos.php:312 mod/photos.php:324 mod/photos.php:1455
msgid "Delete Photo" msgid "Delete Photo"
msgstr "" msgstr ""
@ -7589,149 +7605,149 @@ msgstr ""
msgid "%1$s was tagged in %2$s by %3$s" msgid "%1$s was tagged in %2$s by %3$s"
msgstr "" msgstr ""
#: mod/photos.php:782 #: mod/photos.php:784
msgid "Image upload didn't complete, please try again" msgid "Image upload didn't complete, please try again"
msgstr "" msgstr ""
#: mod/photos.php:785 #: mod/photos.php:787
msgid "Image file is missing" msgid "Image file is missing"
msgstr "" msgstr ""
#: mod/photos.php:790 #: mod/photos.php:792
msgid "" msgid ""
"Server can't accept new file upload at this time, please contact your " "Server can't accept new file upload at this time, please contact your "
"administrator" "administrator"
msgstr "" msgstr ""
#: mod/photos.php:816 #: mod/photos.php:818
msgid "Image file is empty." msgid "Image file is empty."
msgstr "" msgstr ""
#: mod/photos.php:953 #: mod/photos.php:955
msgid "No photos selected" msgid "No photos selected"
msgstr "" msgstr ""
#: mod/photos.php:1104 #: mod/photos.php:1106
msgid "Upload Photos" msgid "Upload Photos"
msgstr "" msgstr ""
#: mod/photos.php:1108 mod/photos.php:1176 #: mod/photos.php:1110 mod/photos.php:1178
msgid "New album name: " msgid "New album name: "
msgstr "" msgstr ""
#: mod/photos.php:1109 #: mod/photos.php:1111
msgid "or select existing album:" msgid "or select existing album:"
msgstr "" msgstr ""
#: mod/photos.php:1110 #: mod/photos.php:1112
msgid "Do not show a status post for this upload" msgid "Do not show a status post for this upload"
msgstr "" msgstr ""
#: mod/photos.php:1187 #: mod/photos.php:1189
msgid "Edit Album" msgid "Edit Album"
msgstr "" msgstr ""
#: mod/photos.php:1192 #: mod/photos.php:1194
msgid "Show Newest First" msgid "Show Newest First"
msgstr "" msgstr ""
#: mod/photos.php:1194 #: mod/photos.php:1196
msgid "Show Oldest First" msgid "Show Oldest First"
msgstr "" msgstr ""
#: mod/photos.php:1215 mod/photos.php:1693 #: mod/photos.php:1217 mod/photos.php:1695
msgid "View Photo" msgid "View Photo"
msgstr "" msgstr ""
#: mod/photos.php:1256 #: mod/photos.php:1258
msgid "Permission denied. Access to this item may be restricted." msgid "Permission denied. Access to this item may be restricted."
msgstr "" msgstr ""
#: mod/photos.php:1258 #: mod/photos.php:1260
msgid "Photo not available" msgid "Photo not available"
msgstr "" msgstr ""
#: mod/photos.php:1333 #: mod/photos.php:1335
msgid "View photo" msgid "View photo"
msgstr "" msgstr ""
#: mod/photos.php:1333 #: mod/photos.php:1335
msgid "Edit photo" msgid "Edit photo"
msgstr "" msgstr ""
#: mod/photos.php:1334 #: mod/photos.php:1336
msgid "Use as profile photo" msgid "Use as profile photo"
msgstr "" msgstr ""
#: mod/photos.php:1340 src/Object/Post.php:151 #: mod/photos.php:1342 src/Object/Post.php:151
msgid "Private Message" msgid "Private Message"
msgstr "" msgstr ""
#: mod/photos.php:1360 #: mod/photos.php:1362
msgid "View Full Size" msgid "View Full Size"
msgstr "" msgstr ""
#: mod/photos.php:1421 #: mod/photos.php:1423
msgid "Tags: " msgid "Tags: "
msgstr "" msgstr ""
#: mod/photos.php:1424 #: mod/photos.php:1426
msgid "[Remove any tag]" msgid "[Remove any tag]"
msgstr "" msgstr ""
#: mod/photos.php:1439 #: mod/photos.php:1441
msgid "New album name" msgid "New album name"
msgstr "" msgstr ""
#: mod/photos.php:1440 #: mod/photos.php:1442
msgid "Caption" msgid "Caption"
msgstr "" msgstr ""
#: mod/photos.php:1441 #: mod/photos.php:1443
msgid "Add a Tag" msgid "Add a Tag"
msgstr "" msgstr ""
#: mod/photos.php:1441 #: mod/photos.php:1443
msgid "Example: @bob, @Barbara_Jensen, @jim@example.com, #California, #camping" msgid "Example: @bob, @Barbara_Jensen, @jim@example.com, #California, #camping"
msgstr "" msgstr ""
#: mod/photos.php:1442 #: mod/photos.php:1444
msgid "Do not rotate" msgid "Do not rotate"
msgstr "" msgstr ""
#: mod/photos.php:1443 #: mod/photos.php:1445
msgid "Rotate CW (right)" msgid "Rotate CW (right)"
msgstr "" msgstr ""
#: mod/photos.php:1444 #: mod/photos.php:1446
msgid "Rotate CCW (left)" msgid "Rotate CCW (left)"
msgstr "" msgstr ""
#: mod/photos.php:1478 src/Object/Post.php:293 #: mod/photos.php:1480 src/Object/Post.php:293
msgid "I like this (toggle)" msgid "I like this (toggle)"
msgstr "" msgstr ""
#: mod/photos.php:1479 src/Object/Post.php:294 #: mod/photos.php:1481 src/Object/Post.php:294
msgid "I don't like this (toggle)" msgid "I don't like this (toggle)"
msgstr "" msgstr ""
#: mod/photos.php:1496 mod/photos.php:1535 mod/photos.php:1595 #: mod/photos.php:1498 mod/photos.php:1537 mod/photos.php:1597
#: src/Object/Post.php:398 src/Object/Post.php:794 #: src/Object/Post.php:398 src/Object/Post.php:794
msgid "Comment" msgid "Comment"
msgstr "" msgstr ""
#: mod/photos.php:1627 #: mod/photos.php:1629
msgid "Map" msgid "Map"
msgstr "" msgstr ""
#: local/test.php:1840 #: local/test.php:1919
#, php-format #, php-format
msgid "" msgid ""
"<span><a href=\"%s\" target=\"_blank\" class=\"shared-wall-item-name\">%s</" "<span><a href=\"%s\" target=\"_blank\" class=\"shared-wall-item-name\">%s</"
"a> wrote the following <a href=\"%s\" target=\"_blank\">post</a>" "a> wrote the following <a href=\"%s\" target=\"_blank\">post</a>"
msgstr "" msgstr ""
#: local/testshare.php:158 src/Content/Text/BBCode.php:991 #: local/testshare.php:158 src/Content/Text/BBCode.php:992
#, php-format #, php-format
msgid "<a href=\"%1$s\" target=\"_blank\">%2$s</a> %3$s" msgid "<a href=\"%1$s\" target=\"_blank\">%2$s</a> %3$s"
msgstr "" msgstr ""
@ -7793,11 +7809,11 @@ msgstr ""
msgid "%s: updating %s table." msgid "%s: updating %s table."
msgstr "" msgstr ""
#: src/Core/Install.php:138 #: src/Core/Install.php:139
msgid "Could not find a command line version of PHP in the web server PATH." msgid "Could not find a command line version of PHP in the web server PATH."
msgstr "" msgstr ""
#: src/Core/Install.php:139 #: src/Core/Install.php:140
msgid "" msgid ""
"If you don't have a command line version of PHP installed on your server, " "If you don't have a command line version of PHP installed on your server, "
"you will not be able to run the background processing. See <a href='https://" "you will not be able to run the background processing. See <a href='https://"
@ -7805,219 +7821,219 @@ msgid ""
"worker'>'Setup the worker'</a>" "worker'>'Setup the worker'</a>"
msgstr "" msgstr ""
#: src/Core/Install.php:143 #: src/Core/Install.php:144
msgid "PHP executable path" msgid "PHP executable path"
msgstr "" msgstr ""
#: src/Core/Install.php:143 #: src/Core/Install.php:144
msgid "" msgid ""
"Enter full path to php executable. You can leave this blank to continue the " "Enter full path to php executable. You can leave this blank to continue the "
"installation." "installation."
msgstr "" msgstr ""
#: src/Core/Install.php:148 #: src/Core/Install.php:149
msgid "Command line PHP" msgid "Command line PHP"
msgstr "" msgstr ""
#: src/Core/Install.php:157 #: src/Core/Install.php:158
msgid "PHP executable is not the php cli binary (could be cgi-fgci version)" msgid "PHP executable is not the php cli binary (could be cgi-fgci version)"
msgstr "" msgstr ""
#: src/Core/Install.php:158 #: src/Core/Install.php:159
msgid "Found PHP version: " msgid "Found PHP version: "
msgstr "" msgstr ""
#: src/Core/Install.php:160 #: src/Core/Install.php:161
msgid "PHP cli binary" msgid "PHP cli binary"
msgstr "" msgstr ""
#: src/Core/Install.php:170 #: src/Core/Install.php:171
msgid "" msgid ""
"The command line version of PHP on your system does not have " "The command line version of PHP on your system does not have "
"\"register_argc_argv\" enabled." "\"register_argc_argv\" enabled."
msgstr "" msgstr ""
#: src/Core/Install.php:171 #: src/Core/Install.php:172
msgid "This is required for message delivery to work." msgid "This is required for message delivery to work."
msgstr "" msgstr ""
#: src/Core/Install.php:173 #: src/Core/Install.php:174
msgid "PHP register_argc_argv" msgid "PHP register_argc_argv"
msgstr "" msgstr ""
#: src/Core/Install.php:201 #: src/Core/Install.php:202
msgid "" msgid ""
"Error: the \"openssl_pkey_new\" function on this system is not able to " "Error: the \"openssl_pkey_new\" function on this system is not able to "
"generate encryption keys" "generate encryption keys"
msgstr "" msgstr ""
#: src/Core/Install.php:202 #: src/Core/Install.php:203
msgid "" msgid ""
"If running under Windows, please see \"http://www.php.net/manual/en/openssl." "If running under Windows, please see \"http://www.php.net/manual/en/openssl."
"installation.php\"." "installation.php\"."
msgstr "" msgstr ""
#: src/Core/Install.php:204 #: src/Core/Install.php:205
msgid "Generate encryption keys" msgid "Generate encryption keys"
msgstr "" msgstr ""
#: src/Core/Install.php:225 #: src/Core/Install.php:226
msgid "libCurl PHP module" msgid "libCurl PHP module"
msgstr "" msgstr ""
#: src/Core/Install.php:226 #: src/Core/Install.php:227
msgid "GD graphics PHP module" msgid "GD graphics PHP module"
msgstr "" msgstr ""
#: src/Core/Install.php:227 #: src/Core/Install.php:228
msgid "OpenSSL PHP module" msgid "OpenSSL PHP module"
msgstr "" msgstr ""
#: src/Core/Install.php:228 #: src/Core/Install.php:229
msgid "PDO or MySQLi PHP module" msgid "PDO or MySQLi PHP module"
msgstr "" msgstr ""
#: src/Core/Install.php:229 #: src/Core/Install.php:230
msgid "mb_string PHP module" msgid "mb_string PHP module"
msgstr "" msgstr ""
#: src/Core/Install.php:230 #: src/Core/Install.php:231
msgid "XML PHP module" msgid "XML PHP module"
msgstr "" msgstr ""
#: src/Core/Install.php:231 #: src/Core/Install.php:232
msgid "iconv PHP module" msgid "iconv PHP module"
msgstr "" msgstr ""
#: src/Core/Install.php:232 #: src/Core/Install.php:233
msgid "POSIX PHP module" msgid "POSIX PHP module"
msgstr "" msgstr ""
#: src/Core/Install.php:236 src/Core/Install.php:238 #: src/Core/Install.php:237 src/Core/Install.php:239
msgid "Apache mod_rewrite module" msgid "Apache mod_rewrite module"
msgstr "" msgstr ""
#: src/Core/Install.php:236 #: src/Core/Install.php:237
msgid "" msgid ""
"Error: Apache webserver mod-rewrite module is required but not installed." "Error: Apache webserver mod-rewrite module is required but not installed."
msgstr "" msgstr ""
#: src/Core/Install.php:244 #: src/Core/Install.php:245
msgid "Error: libCURL PHP module required but not installed." msgid "Error: libCURL PHP module required but not installed."
msgstr "" msgstr ""
#: src/Core/Install.php:248 #: src/Core/Install.php:249
msgid "" msgid ""
"Error: GD graphics PHP module with JPEG support required but not installed." "Error: GD graphics PHP module with JPEG support required but not installed."
msgstr "" msgstr ""
#: src/Core/Install.php:252 #: src/Core/Install.php:253
msgid "Error: openssl PHP module required but not installed." msgid "Error: openssl PHP module required but not installed."
msgstr "" msgstr ""
#: src/Core/Install.php:256 #: src/Core/Install.php:257
msgid "Error: PDO or MySQLi PHP module required but not installed." msgid "Error: PDO or MySQLi PHP module required but not installed."
msgstr "" msgstr ""
#: src/Core/Install.php:260 #: src/Core/Install.php:261
msgid "Error: The MySQL driver for PDO is not installed." msgid "Error: The MySQL driver for PDO is not installed."
msgstr "" msgstr ""
#: src/Core/Install.php:264 #: src/Core/Install.php:265
msgid "Error: mb_string PHP module required but not installed." msgid "Error: mb_string PHP module required but not installed."
msgstr "" msgstr ""
#: src/Core/Install.php:268 #: src/Core/Install.php:269
msgid "Error: iconv PHP module required but not installed." msgid "Error: iconv PHP module required but not installed."
msgstr "" msgstr ""
#: src/Core/Install.php:272 #: src/Core/Install.php:273
msgid "Error: POSIX PHP module required but not installed." msgid "Error: POSIX PHP module required but not installed."
msgstr "" msgstr ""
#: src/Core/Install.php:282 #: src/Core/Install.php:283
msgid "Error, XML PHP module required but not installed." msgid "Error, XML PHP module required but not installed."
msgstr "" msgstr ""
#: src/Core/Install.php:301 #: src/Core/Install.php:302
msgid "" msgid ""
"The web installer needs to be able to create a file called \"local.ini.php\" " "The web installer needs to be able to create a file called \"local.ini.php\" "
"in the \"config\" folder of your web server and it is unable to do so." "in the \"config\" folder of your web server and it is unable to do so."
msgstr "" msgstr ""
#: src/Core/Install.php:302 #: src/Core/Install.php:303
msgid "" msgid ""
"This is most often a permission setting, as the web server may not be able " "This is most often a permission setting, as the web server may not be able "
"to write files in your folder - even if you can." "to write files in your folder - even if you can."
msgstr "" msgstr ""
#: src/Core/Install.php:303 #: src/Core/Install.php:304
msgid "" msgid ""
"At the end of this procedure, we will give you a text to save in a file " "At the end of this procedure, we will give you a text to save in a file "
"named local.ini.php in your Friendica \"config\" folder." "named local.ini.php in your Friendica \"config\" folder."
msgstr "" msgstr ""
#: src/Core/Install.php:304 #: src/Core/Install.php:305
msgid "" msgid ""
"You can alternatively skip this procedure and perform a manual installation. " "You can alternatively skip this procedure and perform a manual installation. "
"Please see the file \"INSTALL.txt\" for instructions." "Please see the file \"INSTALL.txt\" for instructions."
msgstr "" msgstr ""
#: src/Core/Install.php:307 #: src/Core/Install.php:308
msgid "config/local.ini.php is writable" msgid "config/local.ini.php is writable"
msgstr "" msgstr ""
#: src/Core/Install.php:325 #: src/Core/Install.php:326
msgid "" msgid ""
"Friendica uses the Smarty3 template engine to render its web views. Smarty3 " "Friendica uses the Smarty3 template engine to render its web views. Smarty3 "
"compiles templates to PHP to speed up rendering." "compiles templates to PHP to speed up rendering."
msgstr "" msgstr ""
#: src/Core/Install.php:326 #: src/Core/Install.php:327
msgid "" msgid ""
"In order to store these compiled templates, the web server needs to have " "In order to store these compiled templates, the web server needs to have "
"write access to the directory view/smarty3/ under the Friendica top level " "write access to the directory view/smarty3/ under the Friendica top level "
"folder." "folder."
msgstr "" msgstr ""
#: src/Core/Install.php:327 #: src/Core/Install.php:328
msgid "" msgid ""
"Please ensure that the user that your web server runs as (e.g. www-data) has " "Please ensure that the user that your web server runs as (e.g. www-data) has "
"write access to this folder." "write access to this folder."
msgstr "" msgstr ""
#: src/Core/Install.php:328 #: src/Core/Install.php:329
msgid "" msgid ""
"Note: as a security measure, you should give the web server write access to " "Note: as a security measure, you should give the web server write access to "
"view/smarty3/ only--not the template files (.tpl) that it contains." "view/smarty3/ only--not the template files (.tpl) that it contains."
msgstr "" msgstr ""
#: src/Core/Install.php:331 #: src/Core/Install.php:332
msgid "view/smarty3 is writable" msgid "view/smarty3 is writable"
msgstr "" msgstr ""
#: src/Core/Install.php:356 #: src/Core/Install.php:357
msgid "" msgid ""
"Url rewrite in .htaccess is not working. Check your server configuration." "Url rewrite in .htaccess is not working. Check your server configuration."
msgstr "" msgstr ""
#: src/Core/Install.php:358 #: src/Core/Install.php:359
msgid "Error message from Curl when fetching" msgid "Error message from Curl when fetching"
msgstr "" msgstr ""
#: src/Core/Install.php:362 #: src/Core/Install.php:363
msgid "Url rewrite is working" msgid "Url rewrite is working"
msgstr "" msgstr ""
#: src/Core/Install.php:389 #: src/Core/Install.php:390
msgid "ImageMagick PHP extension is not installed" msgid "ImageMagick PHP extension is not installed"
msgstr "" msgstr ""
#: src/Core/Install.php:391 #: src/Core/Install.php:392
msgid "ImageMagick PHP extension is installed" msgid "ImageMagick PHP extension is installed"
msgstr "" msgstr ""
#: src/Core/Install.php:393 #: src/Core/Install.php:394
msgid "ImageMagick supports GIF" msgid "ImageMagick supports GIF"
msgstr "" msgstr ""
@ -8051,11 +8067,16 @@ msgstr ""
msgid "The contact entries have been archived" msgid "The contact entries have been archived"
msgstr "" msgstr ""
#: src/Core/Console/PostUpdate.php:32 #: src/Core/Console/PostUpdate.php:49
#, php-format
msgid "Post update version number has been set to %s."
msgstr ""
#: src/Core/Console/PostUpdate.php:57
msgid "Execute pending post updates." msgid "Execute pending post updates."
msgstr "" msgstr ""
#: src/Core/Console/PostUpdate.php:38 #: src/Core/Console/PostUpdate.php:63
msgid "All pending post updates are done." msgid "All pending post updates are done."
msgstr "" msgstr ""
@ -8115,20 +8136,20 @@ msgstr ""
msgid "%s may attend %s's event" msgid "%s may attend %s's event"
msgstr "" msgstr ""
#: src/Core/NotificationsManager.php:360 #: src/Core/NotificationsManager.php:372
#, php-format #, php-format
msgid "%s is now friends with %s" msgid "%s is now friends with %s"
msgstr "" msgstr ""
#: src/Core/NotificationsManager.php:626 #: src/Core/NotificationsManager.php:638
msgid "Friend Suggestion" msgid "Friend Suggestion"
msgstr "" msgstr ""
#: src/Core/NotificationsManager.php:656 #: src/Core/NotificationsManager.php:672
msgid "Friend/Connect Request" msgid "Friend/Connect Request"
msgstr "" msgstr ""
#: src/Core/NotificationsManager.php:656 #: src/Core/NotificationsManager.php:672
msgid "New Follower" msgid "New Follower"
msgstr "" msgstr ""
@ -8164,7 +8185,7 @@ msgstr[1] ""
msgid "Done. You can now login with your username and password" msgid "Done. You can now login with your username and password"
msgstr "" msgstr ""
#: src/Worker/Delivery.php:423 #: src/Worker/Delivery.php:425
msgid "(no subject)" msgid "(no subject)"
msgstr "" msgstr ""
@ -8299,15 +8320,15 @@ msgstr ""
msgid "Video" msgid "Video"
msgstr "" msgstr ""
#: src/App.php:785 #: src/App.php:798
msgid "Delete this item?" msgid "Delete this item?"
msgstr "" msgstr ""
#: src/App.php:787 #: src/App.php:800
msgid "show fewer" msgid "show fewer"
msgstr "" msgstr ""
#: src/App.php:1385 #: src/App.php:1416
msgid "No system theme config value set." msgid "No system theme config value set."
msgstr "" msgstr ""
@ -8395,43 +8416,47 @@ msgstr ""
msgid "Privacy Statement" msgid "Privacy Statement"
msgstr "" msgstr ""
#: src/Protocol/OStatus.php:1813 #: src/Module/Proxy.php:138
msgid "Bad Request."
msgstr ""
#: src/Protocol/OStatus.php:1823
#, php-format #, php-format
msgid "%s is now following %s." msgid "%s is now following %s."
msgstr "" msgstr ""
#: src/Protocol/OStatus.php:1814 #: src/Protocol/OStatus.php:1824
msgid "following" msgid "following"
msgstr "" msgstr ""
#: src/Protocol/OStatus.php:1817 #: src/Protocol/OStatus.php:1827
#, php-format #, php-format
msgid "%s stopped following %s." msgid "%s stopped following %s."
msgstr "" msgstr ""
#: src/Protocol/OStatus.php:1818 #: src/Protocol/OStatus.php:1828
msgid "stopped following" msgid "stopped following"
msgstr "" msgstr ""
#: src/Protocol/DFRN.php:1525 src/Model/Contact.php:1956 #: src/Protocol/DFRN.php:1528 src/Model/Contact.php:1974
#, php-format #, php-format
msgid "%s's birthday" msgid "%s's birthday"
msgstr "" msgstr ""
#: src/Protocol/DFRN.php:1526 src/Model/Contact.php:1957 #: src/Protocol/DFRN.php:1529 src/Model/Contact.php:1975
#, php-format #, php-format
msgid "Happy Birthday %s" msgid "Happy Birthday %s"
msgstr "" msgstr ""
#: src/Protocol/Diaspora.php:2417 #: src/Protocol/Diaspora.php:2434
msgid "Sharing notification from Diaspora network" msgid "Sharing notification from Diaspora network"
msgstr "" msgstr ""
#: src/Protocol/Diaspora.php:3514 #: src/Protocol/Diaspora.php:3531
msgid "Attachments:" msgid "Attachments:"
msgstr "" msgstr ""
#: src/Util/Temporal.php:147 src/Model/Profile.php:745 #: src/Util/Temporal.php:147 src/Model/Profile.php:758
msgid "Birthday:" msgid "Birthday:"
msgstr "" msgstr ""
@ -8500,134 +8525,134 @@ msgstr ""
msgid "[no subject]" msgid "[no subject]"
msgstr "" msgstr ""
#: src/Model/Contact.php:942 #: src/Model/Contact.php:953
msgid "Drop Contact" msgid "Drop Contact"
msgstr "" msgstr ""
#: src/Model/Contact.php:1399 #: src/Model/Contact.php:1408
msgid "Organisation" msgid "Organisation"
msgstr "" msgstr ""
#: src/Model/Contact.php:1403 #: src/Model/Contact.php:1412
msgid "News" msgid "News"
msgstr "" msgstr ""
#: src/Model/Contact.php:1407 #: src/Model/Contact.php:1416
msgid "Forum" msgid "Forum"
msgstr "" msgstr ""
#: src/Model/Contact.php:1587 #: src/Model/Contact.php:1598
msgid "Connect URL missing." msgid "Connect URL missing."
msgstr "" msgstr ""
#: src/Model/Contact.php:1596 #: src/Model/Contact.php:1607
msgid "" msgid ""
"The contact could not be added. Please check the relevant network " "The contact could not be added. Please check the relevant network "
"credentials in your Settings -> Social Networks page." "credentials in your Settings -> Social Networks page."
msgstr "" msgstr ""
#: src/Model/Contact.php:1635 #: src/Model/Contact.php:1646
msgid "" msgid ""
"This site is not configured to allow communications with other networks." "This site is not configured to allow communications with other networks."
msgstr "" msgstr ""
#: src/Model/Contact.php:1636 src/Model/Contact.php:1650 #: src/Model/Contact.php:1647 src/Model/Contact.php:1661
msgid "No compatible communication protocols or feeds were discovered." msgid "No compatible communication protocols or feeds were discovered."
msgstr "" msgstr ""
#: src/Model/Contact.php:1648 #: src/Model/Contact.php:1659
msgid "The profile address specified does not provide adequate information." msgid "The profile address specified does not provide adequate information."
msgstr "" msgstr ""
#: src/Model/Contact.php:1653 #: src/Model/Contact.php:1664
msgid "An author or name was not found." msgid "An author or name was not found."
msgstr "" msgstr ""
#: src/Model/Contact.php:1656 #: src/Model/Contact.php:1667
msgid "No browser URL could be matched to this address." msgid "No browser URL could be matched to this address."
msgstr "" msgstr ""
#: src/Model/Contact.php:1659 #: src/Model/Contact.php:1670
msgid "" msgid ""
"Unable to match @-style Identity Address with a known protocol or email " "Unable to match @-style Identity Address with a known protocol or email "
"contact." "contact."
msgstr "" msgstr ""
#: src/Model/Contact.php:1660 #: src/Model/Contact.php:1671
msgid "Use mailto: in front of address to force email check." msgid "Use mailto: in front of address to force email check."
msgstr "" msgstr ""
#: src/Model/Contact.php:1666 #: src/Model/Contact.php:1677
msgid "" msgid ""
"The profile address specified belongs to a network which has been disabled " "The profile address specified belongs to a network which has been disabled "
"on this site." "on this site."
msgstr "" msgstr ""
#: src/Model/Contact.php:1671 #: src/Model/Contact.php:1682
msgid "" msgid ""
"Limited profile. This person will be unable to receive direct/personal " "Limited profile. This person will be unable to receive direct/personal "
"notifications from you." "notifications from you."
msgstr "" msgstr ""
#: src/Model/Contact.php:1722 #: src/Model/Contact.php:1733
msgid "Unable to retrieve contact information." msgid "Unable to retrieve contact information."
msgstr "" msgstr ""
#: src/Model/Event.php:59 src/Model/Event.php:76 src/Model/Event.php:428 #: src/Model/Event.php:60 src/Model/Event.php:77 src/Model/Event.php:429
#: src/Model/Event.php:897 #: src/Model/Event.php:904
msgid "Starts:" msgid "Starts:"
msgstr "" msgstr ""
#: src/Model/Event.php:62 src/Model/Event.php:82 src/Model/Event.php:429 #: src/Model/Event.php:63 src/Model/Event.php:83 src/Model/Event.php:430
#: src/Model/Event.php:901 #: src/Model/Event.php:908
msgid "Finishes:" msgid "Finishes:"
msgstr "" msgstr ""
#: src/Model/Event.php:377 #: src/Model/Event.php:378
msgid "all-day" msgid "all-day"
msgstr "" msgstr ""
#: src/Model/Event.php:400 #: src/Model/Event.php:401
msgid "Jun" msgid "Jun"
msgstr "" msgstr ""
#: src/Model/Event.php:403 #: src/Model/Event.php:404
msgid "Sept" msgid "Sept"
msgstr "" msgstr ""
#: src/Model/Event.php:426 #: src/Model/Event.php:427
msgid "No events to display" msgid "No events to display"
msgstr "" msgstr ""
#: src/Model/Event.php:550 #: src/Model/Event.php:551
msgid "l, F j" msgid "l, F j"
msgstr "" msgstr ""
#: src/Model/Event.php:581 #: src/Model/Event.php:582
msgid "Edit event" msgid "Edit event"
msgstr "" msgstr ""
#: src/Model/Event.php:582 #: src/Model/Event.php:583
msgid "Duplicate event" msgid "Duplicate event"
msgstr "" msgstr ""
#: src/Model/Event.php:583 #: src/Model/Event.php:584
msgid "Delete event" msgid "Delete event"
msgstr "" msgstr ""
#: src/Model/Event.php:830 #: src/Model/Event.php:837
msgid "D g:i A" msgid "D g:i A"
msgstr "" msgstr ""
#: src/Model/Event.php:831 #: src/Model/Event.php:838
msgid "g:i A" msgid "g:i A"
msgstr "" msgstr ""
#: src/Model/Event.php:916 src/Model/Event.php:918 #: src/Model/Event.php:923 src/Model/Event.php:925
msgid "Show map" msgid "Show map"
msgstr "" msgstr ""
#: src/Model/Event.php:917 #: src/Model/Event.php:924
msgid "Hide map" msgid "Hide map"
msgstr "" msgstr ""
@ -8707,7 +8732,7 @@ msgstr ""
msgid "An error occurred creating your self contact. Please try again." msgid "An error occurred creating your self contact. Please try again."
msgstr "" msgstr ""
#: src/Model/User.php:561 src/Content/ContactSelector.php:166 #: src/Model/User.php:561 src/Content/ContactSelector.php:171
msgid "Friends" msgid "Friends"
msgstr "" msgstr ""
@ -8810,129 +8835,129 @@ msgstr ""
msgid "Edit groups" msgid "Edit groups"
msgstr "" msgstr ""
#: src/Model/Profile.php:97 #: src/Model/Profile.php:110
msgid "Requested account is not available." msgid "Requested account is not available."
msgstr "" msgstr ""
#: src/Model/Profile.php:163 src/Model/Profile.php:399 #: src/Model/Profile.php:176 src/Model/Profile.php:412
#: src/Model/Profile.php:846 #: src/Model/Profile.php:859
msgid "Edit profile" msgid "Edit profile"
msgstr "" msgstr ""
#: src/Model/Profile.php:333 #: src/Model/Profile.php:346
msgid "Atom feed" msgid "Atom feed"
msgstr "" msgstr ""
#: src/Model/Profile.php:372 #: src/Model/Profile.php:385
msgid "Manage/edit profiles" msgid "Manage/edit profiles"
msgstr "" msgstr ""
#: src/Model/Profile.php:550 src/Model/Profile.php:639 #: src/Model/Profile.php:563 src/Model/Profile.php:652
msgid "g A l F d" msgid "g A l F d"
msgstr "" msgstr ""
#: src/Model/Profile.php:551 #: src/Model/Profile.php:564
msgid "F d" msgid "F d"
msgstr "" msgstr ""
#: src/Model/Profile.php:604 src/Model/Profile.php:690 #: src/Model/Profile.php:617 src/Model/Profile.php:703
msgid "[today]" msgid "[today]"
msgstr "" msgstr ""
#: src/Model/Profile.php:615 #: src/Model/Profile.php:628
msgid "Birthday Reminders" msgid "Birthday Reminders"
msgstr "" msgstr ""
#: src/Model/Profile.php:616 #: src/Model/Profile.php:629
msgid "Birthdays this week:" msgid "Birthdays this week:"
msgstr "" msgstr ""
#: src/Model/Profile.php:677 #: src/Model/Profile.php:690
msgid "[No description]" msgid "[No description]"
msgstr "" msgstr ""
#: src/Model/Profile.php:704 #: src/Model/Profile.php:717
msgid "Event Reminders" msgid "Event Reminders"
msgstr "" msgstr ""
#: src/Model/Profile.php:705 #: src/Model/Profile.php:718
msgid "Upcoming events the next 7 days:" msgid "Upcoming events the next 7 days:"
msgstr "" msgstr ""
#: src/Model/Profile.php:728 #: src/Model/Profile.php:741
msgid "Member since:" msgid "Member since:"
msgstr "" msgstr ""
#: src/Model/Profile.php:736 #: src/Model/Profile.php:749
msgid "j F, Y" msgid "j F, Y"
msgstr "" msgstr ""
#: src/Model/Profile.php:737 #: src/Model/Profile.php:750
msgid "j F" msgid "j F"
msgstr "" msgstr ""
#: src/Model/Profile.php:752 #: src/Model/Profile.php:765
msgid "Age:" msgid "Age:"
msgstr "" msgstr ""
#: src/Model/Profile.php:765 #: src/Model/Profile.php:778
#, php-format #, php-format
msgid "for %1$d %2$s" msgid "for %1$d %2$s"
msgstr "" msgstr ""
#: src/Model/Profile.php:789 #: src/Model/Profile.php:802
msgid "Religion:" msgid "Religion:"
msgstr "" msgstr ""
#: src/Model/Profile.php:797 #: src/Model/Profile.php:810
msgid "Hobbies/Interests:" msgid "Hobbies/Interests:"
msgstr "" msgstr ""
#: src/Model/Profile.php:809 #: src/Model/Profile.php:822
msgid "Contact information and Social Networks:" msgid "Contact information and Social Networks:"
msgstr "" msgstr ""
#: src/Model/Profile.php:813 #: src/Model/Profile.php:826
msgid "Musical interests:" msgid "Musical interests:"
msgstr "" msgstr ""
#: src/Model/Profile.php:817 #: src/Model/Profile.php:830
msgid "Books, literature:" msgid "Books, literature:"
msgstr "" msgstr ""
#: src/Model/Profile.php:821 #: src/Model/Profile.php:834
msgid "Television:" msgid "Television:"
msgstr "" msgstr ""
#: src/Model/Profile.php:825 #: src/Model/Profile.php:838
msgid "Film/dance/culture/entertainment:" msgid "Film/dance/culture/entertainment:"
msgstr "" msgstr ""
#: src/Model/Profile.php:829 #: src/Model/Profile.php:842
msgid "Love/Romance:" msgid "Love/Romance:"
msgstr "" msgstr ""
#: src/Model/Profile.php:833 #: src/Model/Profile.php:846
msgid "Work/employment:" msgid "Work/employment:"
msgstr "" msgstr ""
#: src/Model/Profile.php:837 #: src/Model/Profile.php:850
msgid "School/education:" msgid "School/education:"
msgstr "" msgstr ""
#: src/Model/Profile.php:842 #: src/Model/Profile.php:855
msgid "Forums:" msgid "Forums:"
msgstr "" msgstr ""
#: src/Model/Profile.php:936 #: src/Model/Profile.php:949
msgid "Only You Can See This" msgid "Only You Can See This"
msgstr "" msgstr ""
#: src/Model/Profile.php:944 src/Model/Profile.php:947 #: src/Model/Profile.php:957 src/Model/Profile.php:960
msgid "Tips for New Members" msgid "Tips for New Members"
msgstr "" msgstr ""
#: src/Model/Profile.php:1106 #: src/Model/Profile.php:1119
#, php-format #, php-format
msgid "OpenWebAuth: %1$s welcomes %2$s" msgid "OpenWebAuth: %1$s welcomes %2$s"
msgstr "" msgstr ""
@ -8956,27 +8981,27 @@ msgid_plural "%d invitations available"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#: src/Content/Widget.php:157 #: src/Content/Widget.php:154
msgid "Networks" msgid "Networks"
msgstr "" msgstr ""
#: src/Content/Widget.php:160 #: src/Content/Widget.php:157
msgid "All Networks" msgid "All Networks"
msgstr "" msgstr ""
#: src/Content/Widget.php:198 src/Content/Feature.php:118 #: src/Content/Widget.php:195 src/Content/Feature.php:118
msgid "Saved Folders" msgid "Saved Folders"
msgstr "" msgstr ""
#: src/Content/Widget.php:201 src/Content/Widget.php:241 #: src/Content/Widget.php:198 src/Content/Widget.php:238
msgid "Everything" msgid "Everything"
msgstr "" msgstr ""
#: src/Content/Widget.php:238 #: src/Content/Widget.php:235
msgid "Categories" msgid "Categories"
msgstr "" msgstr ""
#: src/Content/Widget.php:305 #: src/Content/Widget.php:302
#, php-format #, php-format
msgid "%d contact in common" msgid "%d contact in common"
msgid_plural "%d contacts in common" msgid_plural "%d contacts in common"
@ -9052,230 +9077,234 @@ msgid "GNU Social Connector"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:92 #: src/Content/ContactSelector.php:92
msgid "ActivityPub"
msgstr ""
#: src/Content/ContactSelector.php:93
msgid "pnut" msgid "pnut"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:122 #: src/Content/ContactSelector.php:127
msgid "Male" msgid "Male"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:122 #: src/Content/ContactSelector.php:127
msgid "Female" msgid "Female"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:122 #: src/Content/ContactSelector.php:127
msgid "Currently Male" msgid "Currently Male"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:122 #: src/Content/ContactSelector.php:127
msgid "Currently Female" msgid "Currently Female"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:122 #: src/Content/ContactSelector.php:127
msgid "Mostly Male" msgid "Mostly Male"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:122 #: src/Content/ContactSelector.php:127
msgid "Mostly Female" msgid "Mostly Female"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:122 #: src/Content/ContactSelector.php:127
msgid "Transgender" msgid "Transgender"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:122 #: src/Content/ContactSelector.php:127
msgid "Intersex" msgid "Intersex"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:122 #: src/Content/ContactSelector.php:127
msgid "Transsexual" msgid "Transsexual"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:122 #: src/Content/ContactSelector.php:127
msgid "Hermaphrodite" msgid "Hermaphrodite"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:122 #: src/Content/ContactSelector.php:127
msgid "Neuter" msgid "Neuter"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:122 #: src/Content/ContactSelector.php:127
msgid "Non-specific" msgid "Non-specific"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:122 #: src/Content/ContactSelector.php:127
msgid "Other" msgid "Other"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:144 #: src/Content/ContactSelector.php:149
msgid "Males" msgid "Males"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:144 #: src/Content/ContactSelector.php:149
msgid "Females" msgid "Females"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:144 #: src/Content/ContactSelector.php:149
msgid "Gay" msgid "Gay"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:144 #: src/Content/ContactSelector.php:149
msgid "Lesbian" msgid "Lesbian"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:144 #: src/Content/ContactSelector.php:149
msgid "No Preference" msgid "No Preference"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:144 #: src/Content/ContactSelector.php:149
msgid "Bisexual" msgid "Bisexual"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:144 #: src/Content/ContactSelector.php:149
msgid "Autosexual" msgid "Autosexual"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:144 #: src/Content/ContactSelector.php:149
msgid "Abstinent" msgid "Abstinent"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:144 #: src/Content/ContactSelector.php:149
msgid "Virgin" msgid "Virgin"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:144 #: src/Content/ContactSelector.php:149
msgid "Deviant" msgid "Deviant"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:144 #: src/Content/ContactSelector.php:149
msgid "Fetish" msgid "Fetish"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:144 #: src/Content/ContactSelector.php:149
msgid "Oodles" msgid "Oodles"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:144 #: src/Content/ContactSelector.php:149
msgid "Nonsexual" msgid "Nonsexual"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:166 #: src/Content/ContactSelector.php:171
msgid "Single" msgid "Single"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:166 #: src/Content/ContactSelector.php:171
msgid "Lonely" msgid "Lonely"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:166 #: src/Content/ContactSelector.php:171
msgid "Available" msgid "Available"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:166 #: src/Content/ContactSelector.php:171
msgid "Unavailable" msgid "Unavailable"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:166 #: src/Content/ContactSelector.php:171
msgid "Has crush" msgid "Has crush"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:166 #: src/Content/ContactSelector.php:171
msgid "Infatuated" msgid "Infatuated"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:166 #: src/Content/ContactSelector.php:171
msgid "Dating" msgid "Dating"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:166 #: src/Content/ContactSelector.php:171
msgid "Unfaithful" msgid "Unfaithful"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:166 #: src/Content/ContactSelector.php:171
msgid "Sex Addict" msgid "Sex Addict"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:166 #: src/Content/ContactSelector.php:171
msgid "Friends/Benefits" msgid "Friends/Benefits"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:166 #: src/Content/ContactSelector.php:171
msgid "Casual" msgid "Casual"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:166 #: src/Content/ContactSelector.php:171
msgid "Engaged" msgid "Engaged"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:166 #: src/Content/ContactSelector.php:171
msgid "Married" msgid "Married"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:166 #: src/Content/ContactSelector.php:171
msgid "Imaginarily married" msgid "Imaginarily married"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:166 #: src/Content/ContactSelector.php:171
msgid "Partners" msgid "Partners"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:166 #: src/Content/ContactSelector.php:171
msgid "Cohabiting" msgid "Cohabiting"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:166 #: src/Content/ContactSelector.php:171
msgid "Common law" msgid "Common law"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:166 #: src/Content/ContactSelector.php:171
msgid "Happy" msgid "Happy"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:166 #: src/Content/ContactSelector.php:171
msgid "Not looking" msgid "Not looking"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:166 #: src/Content/ContactSelector.php:171
msgid "Swinger" msgid "Swinger"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:166 #: src/Content/ContactSelector.php:171
msgid "Betrayed" msgid "Betrayed"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:166 #: src/Content/ContactSelector.php:171
msgid "Separated" msgid "Separated"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:166 #: src/Content/ContactSelector.php:171
msgid "Unstable" msgid "Unstable"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:166 #: src/Content/ContactSelector.php:171
msgid "Divorced" msgid "Divorced"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:166 #: src/Content/ContactSelector.php:171
msgid "Imaginarily divorced" msgid "Imaginarily divorced"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:166 #: src/Content/ContactSelector.php:171
msgid "Widowed" msgid "Widowed"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:166 #: src/Content/ContactSelector.php:171
msgid "Uncertain" msgid "Uncertain"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:166 #: src/Content/ContactSelector.php:171
msgid "It's complicated" msgid "It's complicated"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:166 #: src/Content/ContactSelector.php:171
msgid "Don't care" msgid "Don't care"
msgstr "" msgstr ""
#: src/Content/ContactSelector.php:166 #: src/Content/ContactSelector.php:171
msgid "Ask me" msgid "Ask me"
msgstr "" msgstr ""
@ -9622,27 +9651,27 @@ msgstr ""
msgid "Embedded content" msgid "Embedded content"
msgstr "" msgstr ""
#: src/Content/Text/BBCode.php:423 #: src/Content/Text/BBCode.php:422
msgid "view full size" msgid "view full size"
msgstr "" msgstr ""
#: src/Content/Text/BBCode.php:853 src/Content/Text/BBCode.php:1626 #: src/Content/Text/BBCode.php:854 src/Content/Text/BBCode.php:1623
#: src/Content/Text/BBCode.php:1627 #: src/Content/Text/BBCode.php:1624
msgid "Image/photo" msgid "Image/photo"
msgstr "" msgstr ""
#: src/Content/Text/BBCode.php:1553 src/Content/Text/BBCode.php:1575 #: src/Content/Text/BBCode.php:1550 src/Content/Text/BBCode.php:1572
msgid "$1 wrote:" msgid "$1 wrote:"
msgstr "" msgstr ""
#: src/Content/Text/BBCode.php:1635 src/Content/Text/BBCode.php:1636 #: src/Content/Text/BBCode.php:1632 src/Content/Text/BBCode.php:1633
msgid "Encrypted content" msgid "Encrypted content"
msgstr "" msgstr ""
#: src/Content/Text/BBCode.php:1755 #: src/Content/Text/BBCode.php:1752
msgid "Invalid source protocol" msgid "Invalid source protocol"
msgstr "" msgstr ""
#: src/Content/Text/BBCode.php:1766 #: src/Content/Text/BBCode.php:1763
msgid "Invalid link protocol" msgid "Invalid link protocol"
msgstr "" msgstr ""