This repository has been archived on 2024-08-19. You can view files and clone it, but cannot push or open issues or pull requests.
core/include/feedutils.php
Klaus Weidenbach b6459e6172 ⬆️ Update SimplePie library.
As a follow up to issue #699 update SimplePie from 1.2.1-dev (around
6years old) to current git master (1.4.4-dev). We use the master branch
until the next release because it contains our patch for enclosure
titles already. The other patches in the library from us can be done by
configuring the SimplePie object in our code instead.
Used composer to manage this library and use class autoloading.
Add some unit tests for include/feedutils.php, but the interesting parts
are unfortunately not testable with the current code.
2017-04-15 00:41:42 +02:00

1501 lines
47 KiB
PHP

<?php
/**
* @brief Return an Atom feed for channel.
*
* @see get_feed_for()
*
* @param array $channel
* @param array $params associative array which configures the feed
* @return string with an atom feed
*/
function get_public_feed($channel, $params) {
/* $type = 'xml';
$begin = NULL_DATE;
$end = '';
$start = 0;
$records = 40;
$direction = 'desc';
$pages = 0;
*/
if(! $params)
$params = array();
$params['type'] = ((x($params,'type')) ? $params['type'] : 'xml');
$params['begin'] = ((x($params,'begin')) ? $params['begin'] : NULL_DATE);
$params['end'] = ((x($params,'end')) ? $params['end'] : datetime_convert('UTC','UTC','now'));
$params['start'] = ((x($params,'start')) ? $params['start'] : 0);
$params['records'] = ((x($params,'records')) ? $params['records'] : 40);
$params['direction'] = ((x($params,'direction')) ? $params['direction'] : 'desc');
$params['pages'] = ((x($params,'pages')) ? intval($params['pages']) : 0);
$params['top'] = ((x($params,'top')) ? intval($params['top']) : 0);
$params['cat'] = ((x($params,'cat')) ? $params['cat'] : '');
// put a sane lower limit on feed requests if not specified
// if($params['begin'] <= NULL_DATE)
// $params['begin'] = datetime_convert('UTC','UTC','now - 1 month');
switch($params['type']) {
case 'json':
header("Content-type: application/atom+json");
break;
case 'xml':
default:
header("Content-type: application/atom+xml");
break;
}
return get_feed_for($channel, get_observer_hash(), $params);
}
/**
* @brief Create an atom feed for $channel from template.
*
* @param array $channel
* @param string $observer_hash xchan_hash from observer
* @param array $params
* @return string with an atom feed
*/
function get_feed_for($channel, $observer_hash, $params) {
if(! channel)
http_status_exit(401);
if($params['pages']) {
if(! perm_is_allowed($channel['channel_id'],$observer_hash,'view_pages'))
http_status_exit(403);
} else {
if(! perm_is_allowed($channel['channel_id'],$observer_hash,'view_stream'))
http_status_exit(403);
}
$items = items_fetch(array(
'wall' => '1',
'datequery' => $params['end'],
'datequery2' => $params['begin'],
'start' => $params['start'], // FIXME
'records' => $params['records'], // FIXME
'direction' => $params['direction'], // FIXME
'pages' => $params['pages'],
'order' => 'post',
'top' => $params['top'],
'cat' => $params['cat']
), $channel, $observer_hash, CLIENT_MODE_NORMAL, App::$module);
$feed_template = get_markup_template('atom_feed.tpl');
$atom = '';
$atom .= replace_macros($feed_template, array(
'$version' => xmlify(Zotlabs\Lib\System::get_project_version()),
'$red' => xmlify(Zotlabs\Lib\System::get_platform_name()),
'$feed_id' => xmlify($channel['xchan_url']),
'$feed_title' => xmlify($channel['channel_name']),
'$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now', ATOM_TIME)),
'$hub' => '', // feed_hublinks(),
'$salmon' => '', // feed_salmonlinks($channel['channel_address']),
'$name' => xmlify($channel['channel_name']),
'$profile_page' => xmlify($channel['xchan_url']),
'$mimephoto' => xmlify($channel['xchan_photo_mimetype']),
'$photo' => xmlify($channel['xchan_photo_l']),
'$thumb' => xmlify($channel['xchan_photo_m']),
'$picdate' => '',
'$uridate' => '',
'$namdate' => '',
'$birthday' => '',
'$community' => '',
));
call_hooks('atom_feed', $atom);
if($items) {
$type = 'html';
foreach($items as $item) {
if($item['item_private'])
continue;
/** @BUG $owner is undefined in this call */
$atom .= atom_entry($item, $type, null, $owner, true);
}
}
call_hooks('atom_feed_end', $atom);
$atom .= '</feed>' . "\r\n";
return $atom;
}
/**
* @brief Return the verb for an item, or fall back to ACTIVITY_POST.
*
* @param array $item an associative array with
* * \e string \b verb
* @return string item's verb if set, default ACTIVITY_POST see boot.php
*/
function construct_verb($item) {
if ($item['verb'])
return $item['verb'];
return ACTIVITY_POST;
}
function construct_activity_object($item) {
if($item['obj']) {
$o = '<as:object>' . "\r\n";
$r = json_decode($item['obj'],false);
if(! $r)
return '';
if($r->type)
$o .= '<as:obj_type>' . xmlify($r->type) . '</as:obj_type>' . "\r\n";
if($r->id)
$o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
if($r->title)
$o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
if($r->links) {
/** @FIXME!! */
if(substr($r->link,0,1) === '<') {
$r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
$o .= $r->link;
}
else
$o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
}
if($r->content) {
$o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
}
$o .= '</as:object>' . "\r\n";
return $o;
}
return '';
}
function construct_activity_target($item) {
if($item['target']) {
$o = '<as:target>' . "\r\n";
$r = json_decode($item['target'],false);
if(! $r)
return '';
if($r->type)
$o .= '<as:obj_type>' . xmlify($r->type) . '</as:obj_type>' . "\r\n";
if($r->id)
$o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
if($r->title)
$o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
if($r->links) {
/** @FIXME !!! */
if(substr($r->link,0,1) === '<') {
if(strstr($r->link,'&') && (! strstr($r->link,'&amp;')))
$r->link = str_replace('&','&amp;', $r->link);
$r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
$o .= $r->link;
}
else
$o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
}
if($r->content)
$o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
$o .= '</as:target>' . "\r\n";
return $o;
}
return '';
}
/**
* @brief Return an array with a parsed atom item.
*
* @param SimplePie $feed
* @param array $item
* @param[out] array $author
* @return array Associative array with the parsed item data
*/
function get_atom_elements($feed, $item, &$author) {
require_once('include/html2bbcode.php');
$res = array();
$found_author = $item->get_author();
if($found_author) {
$author['author_name'] = unxmlify($found_author->get_name());
$author['author_link'] = unxmlify($found_author->get_link());
$author['author_is_feed'] = false;
}
else {
$author['author_name'] = unxmlify($feed->get_title());
$author['author_link'] = unxmlify($feed->get_permalink());
$author['author_is_feed'] = true;
}
if(substr($author['author_link'],-1,1) == '/')
$author['author_link'] = substr($author['author_link'],0,-1);
$res['mid'] = unxmlify($item->get_id());
$res['title'] = unxmlify($item->get_title());
$res['body'] = unxmlify($item->get_content());
$res['plink'] = unxmlify($item->get_link(0));
$res['item_rss'] = 1;
// removing the content of the title if its identically to the body
// This helps with auto generated titles e.g. from tumblr
if (title_is_body($res['title'], $res['body']))
$res['title'] = "";
if($res['plink'])
$base_url = implode('/', array_slice(explode('/',$res['plink']),0,3));
else
$base_url = '';
$rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb');
// select between supported verbs
if($rawverb) {
$res['verb'] = unxmlify($rawverb[0]['data']);
}
// translate OStatus unfollow to activity streams if it happened to get selected
if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow'))
$res['verb'] = ACTIVITY_UNFOLLOW;
if(array_key_exists('verb',$res) && $res['verb'] === ACTIVITY_SHARE) {
// For Mastodon shares ("boosts"), we need to parse the original author information
// from the activity:object -> author structure
$rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
if($rawobj) {
$rawauthor = $rawobj->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name']) {
$author['author_name'] = unxmlify($rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name']);
}
if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri']) {
$author['author_link'] = unxmlify($rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri']);
}
if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
$base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
foreach($base as $link) {
if(!x($author, 'author_photo') || ! $author['author_photo']) {
if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
$author['author_photo'] = unxmlify($link['attribs']['']['href']);
}
}
}
}
}
else {
// look for a photo. We should check media size and find the best one,
// but for now let's just find any author photo
$rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
$base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
foreach($base as $link) {
if(!x($author, 'author_photo') || ! $author['author_photo']) {
if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
$author['author_photo'] = unxmlify($link['attribs']['']['href']);
}
}
}
$rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor');
if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['obj_type'][0]['data'], ACTIVITY_OBJ_PERSON)) {
$base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
if($base && count($base)) {
foreach($base as $link) {
if($link['attribs']['']['rel'] === 'alternate' && (! $res['author_link']))
$author['author_link'] = unxmlify($link['attribs']['']['href']);
if(!x($author, 'author_photo') || ! $author['author_photo']) {
if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
$author['author_photo'] = unxmlify($link['attribs']['']['href']);
}
}
}
}
// check for a yahoo media element (github etc.)
if(! $author['author_photo']) {
$rawmedia = $item->get_item_tags(NAMESPACE_YMEDIA,'thumbnail');
if($rawmedia && $rawmedia[0]['attribs']['']['url']) {
$author['author_photo'] = strip_tags(unxmlify($rawmedia[0]['attribs']['']['url']));
}
}
// No photo/profile-link on the item - look at the feed level
if((! (x($author,'author_link'))) || (! (x($author,'author_photo')))) {
$rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author');
if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
$base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
foreach($base as $link) {
if($link['attribs']['']['rel'] === 'alternate' && (! $author['author_link'])) {
$author['author_link'] = unxmlify($link['attribs']['']['href']);
$author['author_is_feed'] = true;
}
if(! $author['author_photo']) {
if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
$author['author_photo'] = unxmlify($link['attribs']['']['href']);
}
}
}
$rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject');
if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['obj_type'][0]['data'], ACTIVITY_OBJ_PERSON)) {
$base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
if($base && count($base)) {
foreach($base as $link) {
if($link['attribs']['']['rel'] === 'alternate' && (! $res['author_link']))
$author['author_link'] = unxmlify($link['attribs']['']['href']);
if(! (x($author,'author_photo'))) {
if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
$author['author_photo'] = unxmlify($link['attribs']['']['href']);
}
}
}
}
}
}
$ostatus_protocol = (($item->get_item_tags(NAMESPACE_OSTATUS, 'conversation')) ? true : false);
$apps = $item->get_item_tags(NAMESPACE_STATUSNET, 'notice_info');
if($apps && $apps[0]['attribs']['']['source']) {
$res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source']));
}
/*
* If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it.
*/
$have_real_body = false;
$rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env');
if(! $rawenv)
$rawenv = $item->get_item_tags(NAMESPACE_ZOT, 'source');
if($rawenv) {
$have_real_body = true;
$res['body'] = $rawenv[0]['data'];
$res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']);
// make sure nobody is trying to sneak some html tags by us
$res['body'] = notags(base64url_decode($res['body']));
// We could probably turn these old Friendica bbcode bookmarks into bookmark tags but we'd have to
// create a term table item for them. For now just make sure they stay as links.
$res['body'] = preg_replace('/\[bookmark(.*?)\](.*?)\[\/bookmark\]/','[url$1]$2[/url]',$res['body']);
}
$res['body'] = limit_body_size($res['body']);
// It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust
// the content type. Our own network only emits text normally, though it might have been converted to
// html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will
// have to assume it is all html and needs to be purified.
// It doesn't matter all that much security wise - because before this content is used anywhere, we are
// going to escape any tags we find regardless, but this lets us import a limited subset of html from
// the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining
// html.
if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) {
$res['body'] = reltoabs($res['body'],$base_url);
$res['body'] = html2bb_video($res['body']);
$res['body'] = oembed_html2bbcode($res['body']);
$res['body'] = purify_html($res['body']);
$res['body'] = @html2bbcode($res['body']);
}
elseif(! $have_real_body) {
// it's not one of our messages and it has no tags
// so it's probably just text. We'll escape it just to be safe.
$res['body'] = escape_tags($res['body']);
}
// strip title and don't apply "title-in-body" if the feed involved
// uses the OStatus stack. We need a more generalised way for the calling
// function to specify this behaviour or for plugins to alter it.
if($ostatus_protocol) {
$res['title'] = '';
}
elseif($res['plink'] && $res['title']) {
$res['body'] = '#^[url=' . $res['plink'] . ']' . $res['title'] . '[/url]' . "\n\n" . $res['body'];
$terms = array();
$terms[] = array(
'otype' => TERM_OBJ_POST,
'ttype' => TERM_BOOKMARK,
'url' => $res['plink'],
'term' => $res['title'],
);
}
elseif($res['plink']) {
$res['body'] = '#^[url]' . $res['plink'] . '[/url]' . "\n\n" . $res['body'];
$terms = array();
$terms[] = array(
'otype' => TERM_OBJ_POST,
'ttype' => TERM_BOOKMARK,
'url' => $res['plink'],
'term' => $res['plink'],
);
}
$private = $item->get_item_tags(NAMESPACE_DFRN, 'private');
if($private && intval($private[0]['data']) > 0)
$res['item_private'] = ((intval($private[0]['data'])) ? 1 : 0);
else
$res['item_private'] = 0;
$rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location');
if($rawlocation)
$res['location'] = unxmlify($rawlocation[0]['data']);
$rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'published');
if($rawcreated)
$res['created'] = unxmlify($rawcreated[0]['data']);
$rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'updated');
if($rawedited)
$res['edited'] = unxmlify($rawedited[0]['data']);
if((x($res,'edited')) && (! (x($res,'created'))))
$res['created'] = $res['edited'];
if(! $res['created'])
$res['created'] = $item->get_date('c');
if(! $res['edited'])
$res['edited'] = $item->get_date('c');
// Disallow time travelling posts
$d1 = strtotime($res['created']);
$d2 = strtotime($res['edited']);
$d3 = strtotime('now');
if($d1 > $d3)
$res['created'] = datetime_convert();
if($d2 > $d3)
$res['edited'] = datetime_convert();
$res['created'] = datetime_convert('UTC','UTC',$res['created']);
$res['edited'] = datetime_convert('UTC','UTC',$res['edited']);
$rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner');
if(! $rawowner)
$rawowner = $item->get_item_tags(NAMESPACE_ZOT, 'owner');
if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'])
$author['owner_name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']);
elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data'])
$author['owner_name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']);
if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
$author['owner_link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])
$author['owner_link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']);
if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
$base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
foreach($base as $link) {
if(!x($author, 'owner_photo') || ! $author['owner_photo']) {
if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar')
$author['owner_photo'] = unxmlify($link['attribs']['']['href']);
}
}
}
$rawgeo = $item->get_item_tags(NAMESPACE_GEORSS, 'point');
if($rawgeo)
$res['coord'] = unxmlify($rawgeo[0]['data']);
$cats = $item->get_categories();
if($cats) {
if(is_null($terms))
$terms = array();
foreach($cats as $cat) {
$term = $cat->get_term();
if(! $term)
$term = $cat->get_label();
$scheme = $cat->get_scheme();
$termurl = '';
if($scheme && $term && stristr($scheme,'X-DFRN:')) {
$termtype = ((substr($scheme,7,1) === '#') ? TERM_HASHTAG : TERM_MENTION);
$termurl = unxmlify(substr($scheme,9));
}
else {
$termtype = TERM_CATEGORY;
}
$termterm = notags(trim(unxmlify($term)));
if($termterm) {
$terms[] = array(
'otype' => TERM_OBJ_POST,
'ttype' => $termtype,
'url' => $termurl,
'term' => $termterm,
);
}
}
}
if(! is_null($terms))
$res['term'] = $terms;
$attach = $item->get_enclosures();
if($attach) {
$res['attach'] = array();
foreach($attach as $att) {
$len = intval($att->get_length());
$link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link()))));
$title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title()))));
$type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type()))));
if(strpos($type,';'))
$type = substr($type,0,strpos($type,';'));
if((! $link) || (strpos($link,'http') !== 0))
continue;
if(! $title)
$title = ' ';
if(! $type)
$type = 'application/octet-stream';
$res['attach'][] = array('href' => $link, 'length' => $len, 'type' => $type, 'title' => $title );
}
}
$rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object');
if($rawobj) {
$obj = array();
$child = $rawobj[0]['child'];
if($child[NAMESPACE_ACTIVITY]['obj_type'][0]['data']) {
$res['obj_type'] = $child[NAMESPACE_ACTIVITY]['obj_type'][0]['data'];
$obj['type'] = $child[NAMESPACE_ACTIVITY]['obj_type'][0]['data'];
}
if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
$res['obj_type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
$obj['type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
}
if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
$obj['id'] = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'];
if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
$obj['link'] = encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']);
if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
$obj['title'] = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'];
if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
$body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
if(! $body)
$body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
// preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
$obj['orig'] = xmlify($body);
if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
$body = purify_html($body);
$body = html2bbcode($body);
}
$obj['content'] = $body;
}
$res['obj'] = $obj;
}
$rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target');
if($rawobj) {
$obj = array();
$child = $rawobj[0]['child'];
if($child[NAMESPACE_ACTIVITY]['obj_type'][0]['data']) {
$res['tgt_type'] = $child[NAMESPACE_ACTIVITY]['obj_type'][0]['data'];
$obj['type'] = $child[NAMESPACE_ACTIVITY]['obj_type'][0]['data'];
}
if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) {
$res['tgt_type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
$obj['type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'];
}
if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'])
$obj['id'] = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'];
if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link'])
$obj['link'] = encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']);
if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'])
$obj['title'] = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'];
if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) {
$body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data'];
if(! $body)
$body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data'];
// preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events
$obj['orig'] = xmlify($body);
if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) {
$body = purify_html($body);
$body = html2bbcode($body);
}
$obj['content'] = $body;
}
$res['target'] = $obj;
}
// build array to pass to hook
$arr = [
'feed' => $feed,
'item' => $item,
'author' => $author,
'result' => $res
];
call_hooks('parse_atom', $arr);
logger('author: ' .print_r($arr['author'], true), LOGGER_DATA);
logger('result: ' .print_r($arr['result'], true), LOGGER_DATA);
return $arr['result'];
}
/**
* @brief Encodes SimplePie_Item link arrays.
*
* @param array $links Array with SimplePie_Item link tags
* @return array
*/
function encode_rel_links($links) {
$o = array();
if(! ((is_array($links)) && (count($links))))
return $o;
foreach($links as $link) {
$l = array();
if($link['attribs']['']['rel'])
$l['rel'] = $link['attribs']['']['rel'];
if($link['attribs']['']['type'])
$l['type'] = $link['attribs']['']['type'];
if($link['attribs']['']['href'])
$l['href'] = $link['attribs']['']['href'];
if( (x($link['attribs'], NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['width'])
$l['width'] = $link['attribs'][NAMESPACE_MEDIA]['width'];
if( (x($link['attribs'], NAMESPACE_MEDIA)) && $link['attribs'][NAMESPACE_MEDIA]['height'])
$l['height'] = $link['attribs'][NAMESPACE_MEDIA]['height'];
if($l)
$o[] = $l;
}
return $o;
}
/**
* @brief Process atom feed and update anything/everything we might need to update.
*
* @param string $xml
* The (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
* @param $importer
* The contact_record (joined to user_record) of the local user who owns this
* relationship. It is this person's stuff that is going to be updated.
* @param array $contact[in,out]
* The person who is sending us stuff. If not set, we MAY be processing a "follow" activity
* from an external network and MAY create an appropriate contact record. Otherwise, we MUST
* have a contact record.
* @param int $pass by default ($pass = 0) we cannot guarantee that a parent item has been
* imported prior to its children being seen in the stream unless we are certain
* of how the feed is arranged/ordered.
* * With $pass = 1, we only pull parent items out of the stream.
* * With $pass = 2, we only pull children (comments/likes).
*
* So running this twice, first with pass 1 and then with pass 2 will do the right
* thing regardless of feed ordering. This won't be adequate in a fully-threaded
* model where comments can have sub-threads. That would require some massive sorting
* to get all the feed items into a mostly linear ordering, and might still require
* recursion.
*/
function consume_feed($xml, $importer, &$contact, $pass = 0) {
if(! strlen($xml)) {
logger('Empty input');
return;
}
$sys_expire = intval(get_config('system', 'default_expire_days'));
$chn_expire = intval($importer['channel_expire_days']);
$expire_days = $sys_expire;
if(($chn_expire != 0) && ($chn_expire < $sys_expire))
$expire_days = $chn_expire;
$feed = new SimplePie();
$feed->set_raw_data($xml);
// We can preserve iframes because we will strip them in the purifier after
// checking for supported video sources.
$strip_htmltags = $feed->strip_htmltags;
array_splice($strip_htmltags, array_search('iframe', $strip_htmltags), 1);
$feed->strip_htmltags($strip_htmltags);
$feed->init();
if($feed->error())
logger('Error parsing XML: ' . $feed->error());
$permalink = $feed->get_permalink();
// Check at the feed level for updated contact name and/or photo
// process any deleted entries
$del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
if(is_array($del_entries) && count($del_entries) && $pass != 2) {
foreach($del_entries as $dentry) {
$deleted = false;
if(isset($dentry['attribs']['']['ref'])) {
$mid = normalise_id($dentry['attribs']['']['ref']);
$deleted = true;
if(isset($dentry['attribs']['']['when'])) {
$when = $dentry['attribs']['']['when'];
$when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
}
else
$when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
}
if($deleted && is_array($contact)) {
$r = q("SELECT * from item where mid = '%s' and author_xchan = '%s' and uid = %d limit 1",
dbesc($mid),
dbesc($contact['xchan_hash']),
intval($importer['channel_id'])
);
if($r) {
$item = $r[0];
if(! intval($item['item_deleted'])) {
logger('deleting item ' . $item['id'] . ' mid=' . $item['mid'], LOGGER_DEBUG);
drop_item($item['id'],false);
}
}
}
}
}
// Now process the feed
if($feed->get_item_quantity()) {
logger('feed item count = ' . $feed->get_item_quantity(), LOGGER_DEBUG);
$items = $feed->get_items();
foreach($items as $item) {
$is_reply = false;
$item_id = normalise_id($item->get_id());
logger('processing ' . $item->get_id(), LOGGER_DEBUG);
$rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to');
if(isset($rawthread[0]['attribs']['']['ref'])) {
$is_reply = true;
$parent_mid = normalise_id($rawthread[0]['attribs']['']['ref']);
}
if($is_reply) {
if($pass == 1)
continue;
// Have we seen it? If not, import it.
$item_id = normalise_id($item->get_id());
$author = array();
$datarray = get_atom_elements($feed,$item,$author);
if($datarray['mid'])
$datarray['mid'] = normalise_id($item->get_id());
if($contact['xchan_network'] === 'rss') {
$datarray['public_policy'] = 'specific';
$datarray['comment_policy'] = 'none';
}
if((! x($author,'author_name')) || ($author['author_is_feed']))
$author['author_name'] = $contact['xchan_name'];
if((! x($author,'author_link')) || ($author['author_is_feed']))
$author['author_link'] = $contact['xchan_url'];
if((! x($author,'author_photo'))|| ($author['author_is_feed']))
$author['author_photo'] = $contact['xchan_photo_m'];
$datarray['author_xchan'] = '';
if($author['author_link'] != $contact['xchan_url']) {
$x = import_author_unknown(array('name' => $author['author_name'],'url' => $author['author_link'],'photo' => array('src' => $author['author_photo'])));
if($x)
$datarray['author_xchan'] = $x;
}
if(! $datarray['author_xchan'])
$datarray['author_xchan'] = $contact['xchan_hash'];
$datarray['owner_xchan'] = $contact['xchan_hash'];
$r = q("SELECT edited FROM item WHERE mid = '%s' AND uid = %d LIMIT 1",
dbesc($item_id),
intval($importer['channel_id'])
);
// Update content if 'updated' changes
if($r) {
if((x($datarray,'edited') !== false)
&& (datetime_convert('UTC','UTC',$datarray['edited']) !== $r[0]['edited'])) {
// do not accept (ignore) an earlier edit than one we currently have.
if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
continue;
update_feed_item($importer['channel_id'],$datarray);
}
continue;
}
$x = q("select mid from item where mid = '%s' and uid = %d limit 1",
dbesc($parent_mid),
intval($importer['channel_id'])
);
if($x)
$parent_mid = $x[0]['mid'];
$datarray['parent_mid'] = $parent_mid;
$datarray['aid'] = $importer['channel_account_id'];
$datarray['uid'] = $importer['channel_id'];
logger('data: ' . print_r($datarray, true), LOGGER_DATA);
$xx = item_store($datarray);
$r = $xx['item_id'];
continue;
}
else {
// Head post of a conversation. Have we seen it? If not, import it.
$item_id = normalise_id($item->get_id());
$author = array();
$datarray = get_atom_elements($feed,$item,$author);
if($datarray['mid'])
$datarray['mid'] = normalise_id($item->get_id());
if($contact['xchan_network'] === 'rss') {
$datarray['public_policy'] = 'specific';
$datarray['comment_policy'] = 'none';
}
if(is_array($contact)) {
if((! x($author,'author_name')) || ($author['author_is_feed']))
$author['author_name'] = $contact['xchan_name'];
if((! x($author,'author_link')) || ($author['author_is_feed']))
$author['author_link'] = $contact['xchan_url'];
if((! x($author,'author_photo'))|| ($author['author_is_feed']))
$author['author_photo'] = $contact['xchan_photo_m'];
}
if((! x($author,'author_name')) || (! x($author,'author_link'))) {
logger('No author information! ' . print_r($author,true));
continue;
}
$datarray['author_xchan'] = '';
if(activity_match($datarray['verb'],ACTIVITY_FOLLOW) && $datarray['obj_type'] === ACTIVITY_OBJ_PERSON) {
$cb = array('item' => $datarray,'channel' => $importer, 'xchan' => null, 'author' => $author, 'caught' => false);
call_hooks('follow_from_feed',$cb);
if($cb['caught']) {
if($cb['return_code'])
http_status_exit($cb['return_code']);
continue;
}
}
if($author['author_link'] != $contact['xchan_url']) {
$x = import_author_unknown(array('name' => $author['author_name'],'url' => $author['author_link'],'photo' => array('src' => $author['author_photo'])));
if($x)
$datarray['author_xchan'] = $x;
}
if(! $datarray['author_xchan'])
$datarray['author_xchan'] = $contact['xchan_hash'];
$datarray['owner_xchan'] = $contact['xchan_hash'];
if(array_key_exists('created',$datarray) && $datarray['created'] > NULL_DATE && $expire_days) {
$t1 = $datarray['created'];
$t2 = datetime_convert('UTC','UTC','now - ' . $expire_days . 'days');
if($t1 < $t2) {
logger('feed content older than expiration. Ignoring.', LOGGER_DEBUG, LOG_INFO);
continue;
}
}
$r = q("SELECT edited FROM item WHERE mid = '%s' AND uid = %d LIMIT 1",
dbesc($item_id),
intval($importer['channel_id'])
);
// Update content if 'updated' changes
if($r) {
if((x($datarray,'edited') !== false)
&& (datetime_convert('UTC','UTC',$datarray['edited']) !== $r[0]['edited'])) {
// do not accept (ignore) an earlier edit than one we currently have.
if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited'])
continue;
update_feed_item($importer['channel_id'],$datarray);
}
continue;
}
$datarray['parent_mid'] = $item_id;
$datarray['uid'] = $importer['channel_id'];
$datarray['aid'] = $importer['channel_account_id'];
if(! link_compare($author['owner_link'], $contact['xchan_url'])) {
logger('Correcting item owner.', LOGGER_DEBUG);
$author['owner_name'] = $contact['name'];
$author['owner_link'] = $contact['url'];
$author['owner_avatar'] = $contact['thumb'];
}
if(! post_is_importable($datarray, $contact))
continue;
logger('author: ' . print_r($author, true), LOGGER_DEBUG);
logger('data: ' . print_r($datarray, true), LOGGER_DATA);
$xx = item_store($datarray);
$r = $xx['item_id'];
continue;
}
}
}
}
/**
* @brief Normalise an id.
*
* Strip "X-ZOT:" from $id.
*
* @param string $id
* @return string
*/
function normalise_id($id) {
return str_replace('X-ZOT:', '', $id);
}
/**
* @brief Process atom feed and return the first post and structure.
*
* @param string $xml
* The (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
* @param $importer
* The contact_record (joined to user_record) of the local user who owns this
* relationship. It is this person's stuff that is going to be updated.
*/
function process_salmon_feed($xml, $importer) {
$ret = array();
if(! strlen($xml)) {
logger('process_feed: empty input');
return;
}
$feed = new SimplePie();
$feed->set_raw_data($xml);
// We can preserve iframes because we will strip them in the purifier after
// checking for supported video sources.
$strip_htmltags = $feed->strip_htmltags;
array_splice($strip_htmltags, array_search('iframe', $strip_htmltags), 1);
$feed->strip_htmltags($strip_htmltags);
$feed->init();
if($feed->error())
logger('Error parsing XML: ' . $feed->error());
$permalink = $feed->get_permalink();
if($feed->get_item_quantity()) {
// this should be exactly one
logger('feed item count = ' . $feed->get_item_quantity(), LOGGER_DEBUG);
$items = $feed->get_items();
foreach($items as $item) {
$item_id = normalise_id($item->get_id());
logger('processing ' . $item_id, LOGGER_DEBUG);
$rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to');
if(isset($rawthread[0]['attribs']['']['ref'])) {
$is_reply = true;
$parent_mid = normalise_id($rawthread[0]['attribs']['']['ref']);
}
if($is_reply)
$ret['parent_mid'] = $parent_mid;
$ret['author'] = array();
$datarray = get_atom_elements($feed, $item, $ret['author']);
// reset policies which are restricted by default for RSS connections
// This item is likely coming from GNU-social via salmon and allows public interaction
$datarray['public_policy'] = '';
$datarray['comment_policy'] = '';
$ret['item'] = $datarray;
}
}
return $ret;
}
/**
* @brief Given an xml (atom) feed, find author and hub links.
*
* @param string $xml
* @return array
*/
function feed_meta($xml) {
$ret = array();
if(! strlen($xml)) {
logger('empty input');
return $ret;
}
$feed = new SimplePie();
$feed->set_raw_data($xml);
$feed->init();
if($feed->error()) {
logger('Error parsing XML: ' . $feed->error());
return $ret;
}
$ret['hubs'] = $feed->get_links('hub');
//logger('hubs: ' . print_r($hubs,true), LOGGER_DATA);
$author = array();
$found_author = $feed->get_author();
if($found_author) {
$author['author_name'] = unxmlify($found_author->get_name());
$author['author_link'] = unxmlify($found_author->get_link());
$rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
logger('rawauthor: ' . print_r($rawauthor, true));
if($rawauthor) {
if($rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) {
$base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
foreach($base as $link) {
if(!x($author, 'author_photo') || ! $author['author_photo']) {
if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar') {
$author['author_photo'] = unxmlify($link['attribs']['']['href']);
break;
}
}
}
}
if($rawauthor[0]['child'][NAMESPACE_POCO]['displayName'][0]['data'])
$author['full_name'] = unxmlify($rawauthor[0]['child'][NAMESPACE_POCO]['displayName'][0]['data']);
if($rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'])
$author['author_uri'] = unxmlify($rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']);
}
}
if(substr($author['author_link'],-1,1) == '/')
$author['author_link'] = substr($author['author_link'],0,-1);
$ret['author'] = $author;
return $ret;
}
/**
* @brief Not yet implemented function to update feed item.
*
* @param int $uid
* @param array $datarray
*/
function update_feed_item($uid, $datarray) {
logger('Not implemented! ' . $uid . ' ' . print_r($datarray, true), LOGGER_DATA);
}
/**
* @brief Fetch the content of a feed and further consume it.
*
* It will first process parent items and in a second run child items.
* @see consume_feed()
*
* @param int $uid
* @param int $abook_id
* @param string $url URL of the feed
*/
function handle_feed($uid, $abook_id, $url) {
$channel = channelx_by_n($uid);
if(! $channel)
return;
$x = q("select * from abook left join xchan on abook_xchan = xchan_hash where abook_id = %d and abook_channel = %d limit 1",
dbesc($abook_id),
intval($uid)
);
$recurse = 0;
$z = z_fetch_url($url, false, $recurse, array('novalidate' => true));
//logger('data:' . print_r($z, true), LOGGER_DATA);
if($z['success']) {
consume_feed($z['body'], $channel, $x[0], 1);
consume_feed($z['body'], $channel, $x[0], 2);
}
}
/**
* @brief Return a XML tag with author information.
*
* @param string $tag The XML tag to create
* @param string $name Name of the author
* @param string $uri
* @param int $h image height
* @param int $w image width
* @param string $type profile photo mime type
* @param string $photo Fully qualified URL to a profile/avator photo
* @return string
*/
function atom_author($tag, $name, $uri, $h, $w, $type, $photo) {
$o = '';
if(! $tag)
return $o;
$name = xmlify($name);
$uri = xmlify($uri);
$h = intval($h);
$w = intval($w);
$photo = xmlify($photo);
$o .= "<$tag>\r\n";
$o .= "<name>$name</name>\r\n";
$o .= "<uri>$uri</uri>\r\n";
$o .= '<link rel="photo" type="' . $type . '" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
$o .= '<link rel="avatar" type="' . $type . '" media:width="' . $w . '" media:height="' . $h . '" href="' . $photo . '" />' . "\r\n";
call_hooks('atom_author', $o);
$o .= "</$tag>\r\n";
return $o;
}
/**
* @brief Create an item for the Atom feed.
*
* @see get_feed_for()
*
* @param array $item
* @param string $type
* @param array $author
* @param array $owner
* @param string $comment default false
* @param number $cid default 0
* @return void|string
*/
function atom_entry($item, $type, $author, $owner, $comment = false, $cid = 0) {
if(! $item['parent'])
return;
if($item['deleted'])
return '<at:deleted-entry ref="' . xmlify($item['mid']) . '" when="' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '" />' . "\r\n";
create_export_photo_body($item);
if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
$body = fix_private_photos($item['body'],$owner['uid'],$item,$cid);
else
$body = $item['body'];
$o = "\r\n\r\n<entry>\r\n";
if(is_array($author))
$o .= atom_author('author',$author['xchan_name'],$author['xchan_url'],80,80,$author['xchan_photo_mimetype'],$author['xchan_photo_m']);
else
$o .= atom_author('author',$item['author']['xchan_name'],$item['author']['xchan_url'],80,80,$item['author']['xchan_photo_mimetype'], $item['author']['xchan_photo_m']);
$o .= atom_author('zot:owner',$item['owner']['xchan_name'],$item['owner']['xchan_url'],80,80,$item['owner']['xchan_photo_mimetype'],$item['owner']['xchan_photo_m']);
if(($item['parent'] != $item['id']) || ($item['parent_mid'] !== $item['mid']) || (($item['thr_parent'] !== '') && ($item['thr_parent'] !== $item['mid']))) {
$parent_item = (($item['thr_parent']) ? $item['thr_parent'] : $item['parent_mid']);
$o .= '<thr:in-reply-to ref="' . 'X-ZOT:' . xmlify($parent_item) . '" type="text/html" href="' . xmlify($item['plink']) . '" />' . "\r\n";
}
if(activity_match($item['obj_type'],ACTIVITY_OBJ_EVENT) && activity_match($item['verb'],ACTIVITY_POST)) {
$obj = ((is_array($item['obj'])) ? $item['obj'] : json_decode($item['obj'],true));
$o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
$o .= '<summary xmlns="urn:ietf:params:xml:ns:xcal">' . xmlify(bbcode($obj['title'])) . '</summary>' . "\r\n";
$o .= '<dtstart xmlns="urn:ietf:params:xml:ns:xcal">' . datetime_convert('UTC','UTC', $obj['dtstart'],'Ymd\\THis' . (($obj['adjust']) ? '\\Z' : '')) . '</dtstart>' . "\r\n";
$o .= '<dtend xmlns="urn:ietf:params:xml:ns:xcal">' . datetime_convert('UTC','UTC', $obj['dtend'],'Ymd\\THis' . (($obj['adjust']) ? '\\Z' : '')) . '</dtend>' . "\r\n";
$o .= '<location xmlns="urn:ietf:params:xml:ns:xcal">' . xmlify(bbcode($obj['location'])) . '</location>' . "\r\n";
$o .= '<content type="' . $type . '" >' . xmlify(bbcode($obj['description'])) . '</content>' . "\r\n";
}
else {
$o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n";
$o .= '<content type="' . $type . '" >' . xmlify(prepare_text($body,$item['mimetype'])) . '</content>' . "\r\n";
}
$o .= '<id>' . 'X-ZOT:' . xmlify($item['mid']) . '</id>' . "\r\n";
$o .= '<published>' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '</published>' . "\r\n";
$o .= '<updated>' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '</updated>' . "\r\n";
$o .= '<link rel="alternate" type="text/html" href="' . xmlify($item['plink']) . '" />' . "\r\n";
if($item['location']) {
$o .= '<zot:location>' . xmlify($item['location']) . '</zot:location>' . "\r\n";
$o .= '<poco:address><poco:formatted>' . xmlify($item['location']) . '</poco:formatted></poco:address>' . "\r\n";
}
if($item['coord'])
$o .= '<georss:point>' . xmlify($item['coord']) . '</georss:point>' . "\r\n";
if(($item['item_private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']))
$o .= '<zot:private>' . (($item['item_private']) ? $item['item_private'] : 1) . '</zot:private>' . "\r\n";
if($item['app'])
$o .= '<statusnet:notice_info local_id="' . $item['id'] . '" source="' . xmlify($item['app']) . '" ></statusnet:notice_info>' . "\r\n";
$verb = construct_verb($item);
$o .= '<as:verb>' . xmlify($verb) . '</as:verb>' . "\r\n";
$actobj = construct_activity_object($item);
if(strlen($actobj))
$o .= $actobj;
$actarg = construct_activity_target($item);
if(strlen($actarg))
$o .= $actarg;
if($item['attach']) {
$enclosures = json_decode($item['attach'], true);
if($enclosures) {
foreach($enclosures as $enc) {
$o .= '<link rel="enclosure" '
. (($enc['href']) ? 'href="' . $enc['href'] . '" ' : '')
. (($enc['length']) ? 'length="' . $enc['length'] . '" ' : '')
. (($enc['type']) ? 'type="' . $enc['type'] . '" ' : '')
. ' />';
}
}
}
if($item['term']) {
foreach($item['term'] as $term) {
$scheme = '';
$label = '';
switch($term['ttype']) {
case TERM_UNKNOWN:
$scheme = NAMESPACE_ZOT . '/term/unknown';
$label = $term['term'];
break;
case TERM_HASHTAG:
case TERM_COMMUNITYTAG:
$scheme = NAMESPACE_ZOT . '/term/hashtag';
$label = '#' . $term['term'];
break;
case TERM_MENTION:
$scheme = NAMESPACE_ZOT . '/term/mention';
$label = '@' . $term['term'];
break;
case TERM_CATEGORY:
$scheme = NAMESPACE_ZOT . '/term/category';
$label = $term['term'];
break;
default:
break;
}
if(! $scheme)
continue;
$o .= '<category scheme="' . $scheme . '" term="' . $term['term'] . '" label="' . $label . '" />' . "\r\n";
}
}
$o .= '</entry>' . "\r\n";
// build array to pass to hook
$x = [
'item' => $item,
'type' => $type,
'author' => $author,
'owner' => $owner,
'comment' => $comment,
'abook_id' => $cid,
'entry' => $o
];
call_hooks('atom_entry', $x);
return $x['entry'];
}
/**
* @brief
*
* @param array $items
* @return array
*/
function gen_asld($items) {
$ret = array();
if(! $items)
return $ret;
foreach($items as $item) {
$ret[] = i2asld($item);
}
return $ret;
}
/**
* @brief
*
* @param array $i
* @return array
*/
function i2asld($i) {
if(! $i)
return array();
$ret = array();
$ret['@context'] = array( 'http://www.w3.org/ns/activitystreams', 'zot' => 'http://purl.org/zot/protocol');
if($i['verb']) {
if(strpos(dirname($i['verb'],'activitystrea.ms/schema/1.0'))) {
$ret['@type'] = ucfirst(basename($i['verb']));
}
elseif(strpos(dirname($i['verb'],'purl.org/zot'))) {
$ret['@type'] = 'zot:' . ucfirst(basename($i['verb']));
}
}
$ret['@id'] = $i['plink'];
$ret['published'] = datetime_convert('UTC','UTC',$i['created'],ATOM_TIME);
// we need to pass the parent into this
// if($i['id'] != $i['parent'] && $i['obj_type'] === ACTIVITY_OBJ_NOTE) {
// $ret['inReplyTo'] = asencode_note
// }
if($i['obj_type'] === ACTIVITY_OBJ_NOTE)
$ret['object'] = asencode_note($i);
$ret['actor'] = asencode_person($i['author']);
return $ret;
}
function asencode_note($i) {
$ret = array();
$ret['@type'] = 'Note';
$ret['@id'] = $i['plink'];
if($i['title'])
$ret['title'] = bbcode($i['title']);
$ret['content'] = bbcode($i['body']);
$ret['zot:owner'] = asencode_person($i['owner']);
$ret['published'] = datetime_convert('UTC','UTC',$i['created'],ATOM_TIME);
if($i['created'] !== $i['edited'])
$ret['updated'] = datetime_convert('UTC','UTC',$i['edited'],ATOM_TIME);
return $ret;
}
function asencode_person($p) {
$ret = array();
$ret['@type'] = 'Person';
$ret['@id'] = 'acct:' . $p['xchan_addr'];
$ret['displayName'] = $p['xchan_name'];
$ret['icon'] = array(
'@type' => 'Link',
'mediaType' => $p['xchan_photo_mimetype'],
'href' => $p['xchan_photo_m']
);
$ret['url'] = array(
'@type' => 'Link',
'mediaType' => 'text/html',
'href' => $p['xchan_url']
);
return $ret;
}