1501 lines
		
	
	
		
			47 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			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,'&')))
 | |
| 					$r->link = str_replace('&','&', $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' => [ 'placeholder' => '' ], '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;
 | |
| }
 |