Friendica Communications Platform (please note that this is a clone of the repository at github, issues are handled there) https://friendi.ca
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3132 lines
102 KiB

<?php
/**
* @file include/dfrn.php
* @brief The implementation of the dfrn protocol
*
* @see https://github.com/friendica/friendica/wiki/Protocol and
* https://github.com/friendica/friendica/blob/master/spec/dfrn2.pdf
*/
namespace Friendica\Protocol;
use DOMDocument;
use DOMXPath;
use Friendica\App;
use Friendica\Content\OEmbed;
use Friendica\Content\Text\BBCode;
use Friendica\Content\Text\HTML;
use Friendica\Core\Config;
use Friendica\Core\Hook;
use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\Model\Contact;
use Friendica\Model\Conversation;
use Friendica\Model\Event;
use Friendica\Model\GContact;
use Friendica\Model\Item;
use Friendica\Model\PermissionSet;
use Friendica\Model\Profile;
use Friendica\Model\User;
use Friendica\Object\Image;
use Friendica\Util\Crypto;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Network;
use Friendica\Util\Strings;
use Friendica\Util\XML;
use HTMLPurifier;
use HTMLPurifier_Config;
/**
* @brief This class contain functions to create and send DFRN XML files
*/
class DFRN
{
const TOP_LEVEL = 0; // Top level posting
const REPLY = 1; // Regular reply that is stored locally
const REPLY_RC = 2; // Reply that will be relayed
/**
* @brief Generates an array of contact and user for DFRN imports
*
* This array contains not only the receiver but also the sender of the message.
*
* @param integer $cid Contact id
* @param integer $uid User id
*
* @return array importer
* @throws \Exception
*/
public static function getImporter($cid, $uid = 0)
{
$condition = ['id' => $cid, 'blocked' => false, 'pending' => false];
$contact = DBA::selectFirst('contact', [], $condition);
if (!DBA::isResult($contact)) {
return [];
}
$contact['cpubkey'] = $contact['pubkey'];
$contact['cprvkey'] = $contact['prvkey'];
$contact['senderName'] = $contact['name'];
if ($uid != 0) {
$condition = ['uid' => $uid, 'account_expired' => false, 'account_removed' => false];
$user = DBA::selectFirst('user', [], $condition);
if (!DBA::isResult($user)) {
return [];
}
$user['importer_uid'] = $user['uid'];
$user['uprvkey'] = $user['prvkey'];
} else {
$user = ['importer_uid' => 0, 'uprvkey' => '', 'timezone' => 'UTC',
'nickname' => '', 'sprvkey' => '', 'spubkey' => '',
'page-flags' => 0, 'account-type' => 0, 'prvnets' => 0];
}
return array_merge($contact, $user);
}
/**
* @brief Generates the atom entries for delivery.php
*
* This function is used whenever content is transmitted via DFRN.
*
* @param array $items Item elements
* @param array $owner Owner record
*
* @return string DFRN entries
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
* @todo Find proper type-hints
*/
public static function entries($items, $owner)
{
$doc = new DOMDocument('1.0', 'utf-8');
$doc->formatOutput = true;
$root = self::addHeader($doc, $owner, "dfrn:owner", "", false);
if (! count($items)) {
return trim($doc->saveXML());
}
foreach ($items as $item) {
// These values aren't sent when sending from the queue.
/// @todo Check if we can set these values from the queue or if they are needed at all.
$item["entry:comment-allow"] = defaults($item, "entry:comment-allow", true);
$item["entry:cid"] = defaults($item, "entry:cid", 0);
$entry = self::entry($doc, "text", $item, $owner, $item["entry:comment-allow"], $item["entry:cid"]);
$root->appendChild($entry);
}
return trim($doc->saveXML());
}
/**
* @brief Generate an atom feed for the given user
*
* This function is called when another server is pulling data from the user feed.
*
* @param string $dfrn_id DFRN ID from the requesting party
* @param string $owner_nick Owner nick name
* @param string $last_update Date of the last update
* @param int $direction Can be -1, 0 or 1.
* @param boolean $onlyheader Output only the header without content? (Default is "no")
*
* @return string DFRN feed entries
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public static function feed($dfrn_id, $owner_nick, $last_update, $direction = 0, $onlyheader = false)
{
$a = \get_app();
$sitefeed = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic
$public_feed = (($dfrn_id) ? false : true);
$starred = false; // not yet implemented, possible security issues
$converse = false;
if ($public_feed && $a->argc > 2) {
for ($x = 2; $x < $a->argc; $x++) {
if ($a->argv[$x] == 'converse') {
$converse = true;
}
if ($a->argv[$x] == 'starred') {
$starred = true;
}
if ($a->argv[$x] == 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1])) {
$category = $a->argv[$x+1];
}
}
}
// default permissions - anonymous user
$sql_extra = " AND NOT `item`.`private` ";
$r = q(
"SELECT `contact`.*, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`, `user`.`account-type`
FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
WHERE `contact`.`self` AND `user`.`nickname` = '%s' LIMIT 1",
DBA::escape($owner_nick)
);
if (! DBA::isResult($r)) {
Logger::log(sprintf('No contact found for nickname=%d', $owner_nick), Logger::WARNING);
exit();
}
$owner = $r[0];
$owner_id = $owner['uid'];
$sql_post_table = "";
if (! $public_feed) {
switch ($direction) {
case (-1):
$sql_extra = sprintf(" AND `issued-id` = '%s' ", DBA::escape($dfrn_id));
break;
case 0:
$sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", DBA::escape($dfrn_id));
break;
case 1:
$sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", DBA::escape($dfrn_id));
break;
default:
return false;
break; // NOTREACHED
}
$r = q(
"SELECT * FROM `contact` WHERE NOT `blocked` AND `contact`.`uid` = %d $sql_extra LIMIT 1",
intval($owner_id)
);
if (! DBA::isResult($r)) {
Logger::log(sprintf('No contact found for uid=%d', $owner_id), Logger::WARNING);
exit();
}
$contact = $r[0];
$set = PermissionSet::get($owner_id, $contact['id']);
if (!empty($set)) {
$sql_extra = " AND `item`.`psid` IN (" . implode(',', $set) .")";
} else {
$sql_extra = " AND NOT `item`.`private`";
}
}
if ($public_feed) {
$sort = 'DESC';
} else {
$sort = 'ASC';
}
if (! strlen($last_update)) {
$last_update = 'now -30 days';
}
if (isset($category)) {
$sql_post_table = sprintf(
"INNER JOIN (SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d ORDER BY `tid` DESC) AS `term` ON `item`.`id` = `term`.`oid` ",
DBA::escape(Strings::protectSprintf($category)),
intval(TERM_OBJ_POST),
intval(TERM_CATEGORY),
intval($owner_id)
);
}
if ($public_feed && ! $converse) {
$sql_extra .= " AND `contact`.`self` = 1 ";
}
$check_date = DateTimeFormat::utc($last_update);
$r = q(
"SELECT `item`.`id`
FROM `item` USE INDEX (`uid_wall_changed`) $sql_post_table
STRAIGHT_JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
WHERE `item`.`uid` = %d AND `item`.`wall` AND `item`.`changed` > '%s'
$sql_extra
ORDER BY `item`.`parent` ".$sort.", `item`.`created` ASC LIMIT 0, 300",
intval($owner_id),
DBA::escape($check_date),
DBA::escape($sort)
);
$ids = [];
foreach ($r as $item) {
$ids[] = $item['id'];
}
if (!empty($ids)) {
$ret = Item::select(Item::DELIVER_FIELDLIST, ['id' => $ids]);
$items = Item::inArray($ret);
} else {
$items = [];
}
/*
* Will check further below if this actually returned results.
* We will provide an empty feed if that is the case.
*/
$doc = new DOMDocument('1.0', 'utf-8');
$doc->formatOutput = true;
$alternatelink = $owner['url'];
if (isset($category)) {
$alternatelink .= "/category/".$category;
}
if ($public_feed) {
$author = "dfrn:owner";
} else {
$author = "author";
}
$root = self::addHeader($doc, $owner, $author, $alternatelink, true);
/// @TODO This hook can't work anymore
// \Friendica\Core\Hook::callAll('atom_feed', $atom);
if (!DBA::isResult($items) || $onlyheader) {
$atom = trim($doc->saveXML());
Hook::callAll('atom_feed_end', $atom);
return $atom;
}
foreach ($items as $item) {
// prevent private email from leaking.
if ($item['network'] == Protocol::MAIL) {
continue;
}
// public feeds get html, our own nodes use bbcode
if ($public_feed) {
$type = 'html';
// catch any email that's in a public conversation and make sure it doesn't leak
if ($item['private']) {
continue;
}
} else {
$type = 'text';
}
$entry = self::entry($doc, $type, $item, $owner, true);
$root->appendChild($entry);
}
$atom = trim($doc->saveXML());
Hook::callAll('atom_feed_end', $atom);
return $atom;
}
/**
* @brief Generate an atom entry for a given item id
*
* @param int $item_id The item id
* @param boolean $conversation Show the conversation. If false show the single post.
*
* @return string DFRN feed entry
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public static function itemFeed($item_id, $conversation = false)
{
if ($conversation) {
$condition = ['parent' => $item_id];
} else {
$condition = ['id' => $item_id];
}
$ret = Item::select(Item::DELIVER_FIELDLIST, $condition);
$items = Item::inArray($ret);
if (!DBA::isResult($items)) {
exit();
}
$item = $items[0];
if ($item['uid'] != 0) {
$owner = User::getOwnerDataById($item['uid']);
if (!$owner) {
exit();
}
4 years ago
} else {
$owner = ['uid' => 0, 'nick' => 'feed-item'];
}
$doc = new DOMDocument('1.0', 'utf-8');
$doc->formatOutput = true;
$type = 'html';
if ($conversation) {
$root = $doc->createElementNS(NAMESPACE_ATOM1, 'feed');
$doc->appendChild($root);
$root->setAttribute("xmlns:thr", NAMESPACE_THREAD);
$root->setAttribute("xmlns:at", NAMESPACE_TOMB);
$root->setAttribute("xmlns:media", NAMESPACE_MEDIA);
$root->setAttribute("xmlns:dfrn", NAMESPACE_DFRN);
$root->setAttribute("xmlns:activity", NAMESPACE_ACTIVITY);
$root->setAttribute("xmlns:georss", NAMESPACE_GEORSS);
$root->setAttribute("xmlns:poco", NAMESPACE_POCO);
$root->setAttribute("xmlns:ostatus", NAMESPACE_OSTATUS);
$root->setAttribute("xmlns:statusnet", NAMESPACE_STATUSNET);
//$root = self::addHeader($doc, $owner, "dfrn:owner", "", false);
foreach ($items as $item) {
$entry = self::entry($doc, $type, $item, $owner, true, 0);
$root->appendChild($entry);
}
} else {
$root = self::entry($doc, $type, $item, $owner, true, 0, true);
}
$atom = trim($doc->saveXML());
return $atom;
}
/**
* @brief Create XML text for DFRN mails
*
* @param array $item message elements
* @param array $owner Owner record
*
* @return string DFRN mail
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @todo Find proper type-hints
*/
public static function mail($item, $owner)
{
$doc = new DOMDocument('1.0', 'utf-8');
$doc->formatOutput = true;
$root = self::addHeader($doc, $owner, "dfrn:owner", "", false);
$mail = $doc->createElement("dfrn:mail");
$sender = $doc->createElement("dfrn:sender");
XML::addElement($doc, $sender, "dfrn:name", $owner['name']);
XML::addElement($doc, $sender, "dfrn:uri", $owner['url']);
XML::addElement($doc, $sender, "dfrn:avatar", $owner['thumb']);
$mail->appendChild($sender);
XML::addElement($doc, $mail, "dfrn:id", $item['uri']);
XML::addElement($doc, $mail, "dfrn:in-reply-to", $item['parent-uri']);
XML::addElement($doc, $mail, "dfrn:sentdate", DateTimeFormat::utc($item['created'] . '+00:00', DateTimeFormat::ATOM));
XML::addElement($doc, $mail, "dfrn:subject", $item['title']);
XML::addElement($doc, $mail, "dfrn:content", $item['body']);
$root->appendChild($mail);
return trim($doc->saveXML());
}
/**
* @brief Create XML text for DFRN friend suggestions
*
* @param array $item suggestion elements
* @param array $owner Owner record
*
* @return string DFRN suggestions
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @todo Find proper type-hints
*/
public static function fsuggest($item, $owner)
{
$doc = new DOMDocument('1.0', 'utf-8');
$doc->formatOutput = true;
$root = self::addHeader($doc, $owner, "dfrn:owner", "", false);
$suggest = $doc->createElement("dfrn:suggest");
XML::addElement($doc, $suggest, "dfrn:url", $item['url']);
XML::addElement($doc, $suggest, "dfrn:name", $item['name']);
XML::addElement($doc, $suggest, "dfrn:photo", $item['photo']);
XML::addElement($doc, $suggest, "dfrn:request", $item['request']);
XML::addElement($doc, $suggest, "dfrn:note", $item['note']);
$root->appendChild($suggest);
return trim($doc->saveXML());
}
/**
* @brief Create XML text for DFRN relocations
*
* @param array $owner Owner record
* @param int $uid User ID
*
* @return string DFRN relocations
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @todo Find proper type-hints
*/
public static function relocate($owner, $uid)
{
/* get site pubkey. this could be a new installation with no site keys*/
$pubkey = Config::get('system', 'site_pubkey');
if (! $pubkey) {
$res = Crypto::newKeypair(1024);
Config::set('system', 'site_prvkey', $res['prvkey']);
Config::set('system', 'site_pubkey', $res['pubkey']);
}
$rp = q(
"SELECT `resource-id` , `scale`, type FROM `photo`
WHERE `profile` = 1 AND `uid` = %d ORDER BY scale;",
$uid
);
$photos = [];
$ext = Image::supportedTypes();
foreach ($rp as $p) {
$photos[$p['scale']] = System::baseUrl().'/photo/'.$p['resource-id'].'-'.$p['scale'].'.'.$ext[$p['type']];
}
$doc = new DOMDocument('1.0', 'utf-8');
$doc->formatOutput = true;
$root = self::addHeader($doc, $owner, "dfrn:owner", "", false);
$relocate = $doc->createElement("dfrn:relocate");
XML::addElement($doc, $relocate, "dfrn:url", $owner['url']);
XML::addElement($doc, $relocate, "dfrn:name", $owner['name']);
XML::addElement($doc, $relocate, "dfrn:addr", $owner['addr']);
XML::addElement($doc, $relocate, "dfrn:avatar", $owner['avatar']);
XML::addElement($doc, $relocate, "dfrn:photo", $photos[4]);
XML::addElement($doc, $relocate, "dfrn:thumb", $photos[5]);
XML::addElement($doc, $relocate, "dfrn:micro", $photos[6]);
XML::addElement($doc, $relocate, "dfrn:request", $owner['request']);
XML::addElement($doc, $relocate, "dfrn:confirm", $owner['confirm']);
XML::addElement($doc, $relocate, "dfrn:notify", $owner['notify']);
XML::addElement($doc, $relocate, "dfrn:poll", $owner['poll']);
XML::addElement($doc, $relocate, "dfrn:sitepubkey", Config::get('system', 'site_pubkey'));
$root->appendChild($relocate);
return trim($doc->saveXML());
}
/**
* @brief Adds the header elements for the DFRN protocol
*
* @param DOMDocument $doc XML document
* @param array $owner Owner record
* @param string $authorelement Element name for the author
* @param string $alternatelink link to profile or category
* @param bool $public Is it a header for public posts?
*
* @return object XML root object
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @todo Find proper type-hints
*/
private static function addHeader(DOMDocument $doc, $owner, $authorelement, $alternatelink = "", $public = false)
{
if ($alternatelink == "") {
$alternatelink = $owner['url'];
}
$root = $doc->createElementNS(NAMESPACE_ATOM1, 'feed');
$doc->appendChild($root);
$root->setAttribute("xmlns:thr", NAMESPACE_THREAD);
$root->setAttribute("xmlns:at", NAMESPACE_TOMB);
$root->setAttribute("xmlns:media", NAMESPACE_MEDIA);
$root->setAttribute("xmlns:dfrn", NAMESPACE_DFRN);
$root->setAttribute("xmlns:activity", NAMESPACE_ACTIVITY);
$root->setAttribute("xmlns:georss", NAMESPACE_GEORSS);
$root->setAttribute("xmlns:poco", NAMESPACE_POCO);
$root->setAttribute("xmlns:ostatus", NAMESPACE_OSTATUS);
$root->setAttribute("xmlns:statusnet", NAMESPACE_STATUSNET);
XML::addElement($doc, $root, "id", System::baseUrl()."/profile/".$owner["nick"]);
XML::addElement($doc, $root, "title", $owner["name"]);
$attributes = ["uri" => "https://friendi.ca", "version" => FRIENDICA_VERSION."-".DB_UPDATE_VERSION];
XML::addElement($doc, $root, "generator", FRIENDICA_PLATFORM, $attributes);
$attributes = ["rel" => "license", "href" => "http://creativecommons.org/licenses/by/3.0/"];
XML::addElement($doc, $root, "link", "", $attributes);
$attributes = ["rel" => "alternate", "type" => "text/html", "href" => $alternatelink];
XML::addElement($doc, $root, "link", "", $attributes);
if ($public) {
// DFRN itself doesn't uses this. But maybe someone else wants to subscribe to the public feed.
OStatus::hublinks($doc, $root, $owner["nick"]);
$attributes = ["rel" => "salmon", "href" => System::baseUrl()."/salmon/".$owner["nick"]];
XML::addElement($doc, $root, "link", "", $attributes);
$attributes = ["rel" => "http://salmon-protocol.org/ns/salmon-replies", "href" => System::baseUrl()."/salmon/".$owner["nick"]];
XML::addElement($doc, $root, "link", "", $attributes);
$attributes = ["rel" => "http://salmon-protocol.org/ns/salmon-mention", "href" => System::baseUrl()."/salmon/".$owner["nick"]];
XML::addElement($doc, $root, "link", "", $attributes);
}
// For backward compatibility we keep this element
if ($owner['page-flags'] == User::PAGE_FLAGS_COMMUNITY) {
XML::addElement($doc, $root, "dfrn:community", 1);
}
// The former element is replaced by this one
XML::addElement($doc, $root, "dfrn:account_type", $owner["account-type"]);
/// @todo We need a way to transmit the different page flags like "User::PAGE_FLAGS_PRVGROUP"
XML::addElement($doc, $root, "updated", DateTimeFormat::utcNow(DateTimeFormat::ATOM));
$author = self::addAuthor($doc, $owner, $authorelement, $public);
$root->appendChild($author);
return $root;
}
/**
* @brief Adds the author element in the header for the DFRN protocol
*
* @param DOMDocument $doc XML document
* @param array $owner Owner record
* @param string $authorelement Element name for the author
* @param boolean $public boolean
*
* @return \DOMElement XML author object
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @todo Find proper type-hints
*/
private static function addAuthor(DOMDocument $doc, array $owner, $authorelement, $public)
{
// Is the profile hidden or shouldn't be published in the net? Then add the "hide" element
$r = q(
"SELECT `id` FROM `profile` INNER JOIN `user` ON `user`.`uid` = `profile`.`uid`
WHERE (`hidewall` OR NOT `net-publish`) AND `user`.`uid` = %d",
intval($owner['uid'])
);
if (DBA::isResult($r)) {
$hidewall = true;
} else {
$hidewall = false;
}
$author = $doc->createElement($authorelement);
$namdate = DateTimeFormat::utc($owner['name-date'].'+00:00', DateTimeFormat::ATOM);
$uridate = DateTimeFormat::utc($owner['uri-date'].'+00:00', DateTimeFormat::ATOM);
$picdate = DateTimeFormat::utc($owner['avatar-date'].'+00:00', DateTimeFormat::ATOM);
$attributes = [];
if (!$public || !$hidewall) {
$attributes = ["dfrn:updated" => $namdate];
}
XML::addElement($doc, $author, "name", $owner["name"], $attributes);
XML::addElement($doc, $author, "uri", System::baseUrl().'/profile/'.$owner["nickname"], $attributes);
XML::addElement($doc, $author, "dfrn:handle", $owner["addr"], $attributes);
$attributes = ["rel" => "photo", "type" => "image/jpeg",
"media:width" => 300, "media:height" => 300, "href" => $owner['photo']];
if (!$public || !$hidewall) {
$attributes["dfrn:updated"] = $picdate;
}
XML::addElement($doc, $author, "link", "", $attributes);
$attributes["rel"] = "avatar";
XML::addElement($doc, $author, "link", "", $attributes);
if ($hidewall) {
XML::addElement($doc, $author, "dfrn:hide", "true");
}
// The following fields will only be generated if the data isn't meant for a public feed
if ($public) {
return $author;
}
$birthday = feed_birthday($owner['uid'], $owner['timezone']);
if ($birthday) {
XML::addElement($doc, $author, "dfrn:birthday", $birthday);
}
// Only show contact details when we are allowed to
$r = q(
"SELECT `profile`.`about`, `profile`.`name`, `profile`.`homepage`, `user`.`nickname`,
`user`.`timezone`, `profile`.`locality`, `profile`.`region`, `profile`.`country-name`,
`profile`.`pub_keywords`, `profile`.`xmpp`, `profile`.`dob`
FROM `profile`
INNER JOIN `user` ON `user`.`uid` = `profile`.`uid`
WHERE `profile`.`is-default` AND NOT `user`.`hidewall` AND `user`.`uid` = %d",
intval($owner['uid'])
);
if (DBA::isResult($r)) {
$profile = $r[0];
XML::addElement($doc, $author, "poco:displayName", $profile["name"]);
XML::addElement($doc, $author, "poco:updated", $namdate);
if (trim($profile["dob"]) > DBA::NULL_DATE) {
XML::addElement($doc, $author, "poco:birthday", "0000-".date("m-d", strtotime($profile["dob"])));
}
XML::addElement($doc, $author, "poco:note", $profile["about"]);
XML::addElement($doc, $author, "poco:preferredUsername", $profile["nickname"]);
$savetz = date_default_timezone_get();
date_default_timezone_set($profile["timezone"]);
XML::addElement($doc, $author, "poco:utcOffset", date("P"));
date_default_timezone_set($savetz);
if (trim($profile["homepage"]) != "") {
$urls = $doc->createElement("poco:urls");
XML::addElement($doc, $urls, "poco:type", "homepage");
XML::addElement($doc, $urls, "poco:value", $profile["homepage"]);
XML::addElement($doc, $urls, "poco:primary", "true");
$author->appendChild($urls);
}
if (trim($profile["pub_keywords"]) != "") {
$keywords = explode(",", $profile["pub_keywords"]);
foreach ($keywords as $keyword) {
XML::addElement($doc, $author, "poco:tags", trim($keyword));
}
}
if (trim($profile["xmpp"]) != "") {
$ims = $doc->createElement("poco:ims");
XML::addElement($doc, $ims, "poco:type", "xmpp");
XML::addElement($doc, $ims, "poco:value", $profile["xmpp"]);
XML::addElement($doc, $ims, "poco:primary", "true");
$author->appendChild($ims);
}
if (trim($profile["locality"].$profile["region"].$profile["country-name"]) != "") {
$element = $doc->createElement("poco:address");
XML::addElement($doc, $element, "poco:formatted", Profile::formatLocation($profile));
if (trim($profile["locality"]) != "") {
XML::addElement($doc, $element, "poco:locality", $profile["locality"]);
}
if (trim($profile["region"]) != "") {
XML::addElement($doc, $element, "poco:region", $profile["region"]);
}
if (trim($profile["country-name"]) != "") {
XML::addElement($doc, $element, "poco:country", $profile["country-name"]);
}
$author->appendChild($element);
}
}
return $author;
}
/**
* @brief Adds the author elements in the "entry" elements of the DFRN protocol
*
* @param DOMDocument $doc XML document
* @param string $element Element name for the author
* @param string $contact_url Link of the contact
* @param array $item Item elements
*
* @return \DOMElement XML author object
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @todo Find proper type-hints
*/
private static function addEntryAuthor(DOMDocument $doc, $element, $contact_url, $item)
{
$contact = Contact::getDetailsByURL($contact_url, $item["uid"]);
$author = $doc->createElement($element);
XML::addElement($doc, $author, "name", $contact["name"]);
XML::addElement($doc, $author, "uri", $contact["url"]);
XML::addElement($doc, $author, "dfrn:handle", $contact["addr"]);
/// @Todo
/// - Check real image type and image size
/// - Check which of these boths elements we should use
$attributes = [
"rel" => "photo",
"type" => "image/jpeg",
"media:width" => 80,
"media:height" => 80,
"href" => $contact["photo"]];
XML::addElement($doc, $author, "link", "", $attributes);
$attributes = [
"rel" => "avatar",
"type" => "image/jpeg",
"media:width" => 80,
"media:height" => 80,
"href" => $contact["photo"]];
XML::addElement($doc, $author, "link", "", $attributes);
return $author;
}
/**
* @brief Adds the activity elements
*
* @param DOMDocument $doc XML document
* @param string $element Element name for the activity
* @param string $activity activity value
*
* @return \DOMElement XML activity object
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @todo Find proper type-hints
*/
private static function createActivity(DOMDocument $doc, $element, $activity)
{
if ($activity) {
$entry = $doc->createElement($element);
$r = XML::parseString($activity, false);
if (!$r) {
return false;
}
if ($r->type) {
XML::addElement($doc, $entry, "activity:object-type", $r->type);
}
if ($r->id) {
XML::addElement($doc, $entry, "id", $r->id);
}
if ($r->title) {
XML::addElement($doc, $entry, "title", $r->title);
}
if ($r->link) {
if (substr($r->link, 0, 1) == '<') {
if (strstr($r->link, '&') && (! strstr($r->link, '&amp;'))) {
$r->link = str_replace('&', '&amp;', $r->link);