xmlify(Zotlabs\Lib\System::get_project_version()), '$generator' => xmlify(Zotlabs\Lib\System::get_platform_name()), '$generator_uri' => 'https://framagit.org/zot/zap', '$feed_id' => xmlify($channel['xchan_url']), '$feed_title' => xmlify($channel['channel_name']), '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now', ATOM_TIME)), '$author' => $feed_author, '$owner' => $owner, '$profile_page' => xmlify($channel['xchan_url']), )); $x = [ 'xml' => $atom, 'channel' => $channel, 'observer_hash' => $observer_hash, 'params' => $params ]; /** * @hooks atom_feed_top * * \e string \b xml - the generated feed and what will get returned from the hook * * \e array \b channel * * \e string \b observer_hash * * \e array \b params */ call_hooks('atom_feed_top', $x); $atom = $x['xml']; /** * @hooks atom_feed * A much simpler interface than atom_feed_top. * * \e string - the feed after atom_feed_top hook */ call_hooks('atom_feed', $atom); $items = items_fetch( [ 'wall' => '1', 'datequery' => $params['end'], 'datequery2' => $params['begin'], 'start' => intval($params['start']), 'records' => intval($params['records']), 'direction' => dbesc($params['direction']), 'pages' => $params['pages'], 'order' => dbesc('post'), 'top' => $params['top'], 'cat' => $params['cat'], 'compat' => $params['compat'] ], $channel, $observer_hash, CLIENT_MODE_NORMAL, App::$module ); if($items) { $type = 'html'; foreach($items as $item) { if($item['item_private']) continue; $atom .= atom_entry($item, $type, null, $owner, true, '', $params['compat']); } } /** * @hooks atom_feed_end * \e string - The created XML feed as a string without closing tag */ call_hooks('atom_feed_end', $atom); $atom .= '' . "\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 = '' . "\r\n"; $r = json_decode($item['obj'],false); if(! $r) return ''; if($r->type) $o .= '' . xmlify($r->type) . '' . "\r\n"; if($r->id) $o .= '' . xmlify($r->id) . '' . "\r\n"; if($r->title) $o .= '' . xmlify($r->title) . '' . "\r\n"; if($r->links) { /** @FIXME!! */ if(substr($r->link,0,1) === '<') { $r->link = preg_replace('/\/','',$r->link); $o .= $r->link; } else $o .= '' . "\r\n"; } if($r->content) { $o .= '' . xmlify(bbcode($r->content)) . '' . "\r\n"; } $o .= '' . "\r\n"; return $o; } return ''; } function construct_activity_target($item) { if($item['target']) { $o = '' . "\r\n"; $r = json_decode($item['target'],false); if(! $r) return ''; if($r->type) $o .= '' . xmlify($r->type) . '' . "\r\n"; if($r->id) $o .= '' . xmlify($r->id) . '' . "\r\n"; if($r->title) $o .= '' . xmlify($r->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('/\/','',$r->link); $o .= $r->link; } else $o .= '' . "\r\n"; } if($r->content) $o .= '' . xmlify(bbcode($r->content)) . '' . "\r\n"; $o .= '' . "\r\n"; return $o; } return ''; } /** * @brief Return a XML tag with author information. * * @param string $tag The XML tag to create * @param string $nick preferred username * @param string $name displayed 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 XML tag */ function atom_author($tag, $nick, $name, $uri, $h, $w, $type, $photo) { $o = ''; if(! $tag) return $o; $nick = xmlify($nick); $name = xmlify($name); $uri = xmlify($uri); $h = intval($h); $w = intval($w); $photo = xmlify($photo); $o .= "<$tag>\r\n"; $o .= " $uri\r\n"; $o .= " $nick\r\n"; $o .= " $uri\r\n"; $o .= ' ' . "\r\n"; $o .= ' ' . "\r\n"; $o .= ' ' . $nick . '' . "\r\n"; $o .= ' ' . $name . '' . "\r\n"; /** * @hooks atom_author * Possibility to add further tags to returned XML string * * \e string - The created XML tag as a string without closing tag */ call_hooks('atom_author', $o); $o .= "\r\n"; return $o; } /** * @brief Return an atom tag with author information from an xchan. * * @param string $tag * @param array $xchan * @return string */ function atom_render_author($tag, $xchan) { $nick = xmlify(substr($xchan['xchan_addr'], 0, strpos($xchan['xchan_addr'], '@'))); $id = xmlify($xchan['xchan_url']); $name = xmlify($xchan['xchan_name']); $photo = xmlify($xchan['xchan_photo_l']); $type = xmlify($xchan['xchan_photo_mimetype']); $w = $h = 300; $o = "<$tag>\r\n"; $o .= " http://activitystrea.ms/schema/1.0/person\r\n"; $o .= " $id\r\n"; $o .= " $nick\r\n"; $o .= " $id\r\n"; $o .= ' ' . "\r\n"; $o .= ' ' . "\r\n"; $o .= ' ' . "\r\n"; $o .= ' ' . $nick . '' . "\r\n"; $o .= ' ' . $name . '' . "\r\n"; /** * @hooks atom_render_author * Possibility to add further tags to returned XML string. * * \e string The created XML tag as a string without closing tag */ call_hooks('atom_render_author', $o); $o .= "\r\n"; return $o; } function compat_photos_list($s) { $ret = []; $found = preg_match_all('/\[[zi]mg(.*?)\](.*?)\[/ism',$s,$matches,PREG_SET_ORDER); if($found) { foreach($matches as $match) { $entry = [ 'href' => $match[2], 'type' => guess_image_type($match[2]) ]; $sizer = new \Zotlabs\Lib\Img_filesize($match[2]); $size = $sizer->getSize(); if(intval($size)) { $entry['length'] = intval($size); } $ret[] = $entry; } } return $ret; } /** * @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 * @param boolean $compat default false * @return void|string */ function atom_entry($item, $type, $author, $owner, $comment = false, $cid = 0, $compat = false) { if(! $item['parent']) return; if($item['deleted']) return '' . "\r\n"; create_export_photo_body($item); // provide separate summary and content unless compat is true; as summary represents a content-warning on some networks $matches = false; if(preg_match('|\[summary\](.*?)\[/summary\]|ism',$item['body'],$matches)) $summary = $matches[1]; else $summary = ''; $body = $item['body']; if($summary) $body = preg_replace('|^(.*?)\[summary\](.*?)\[/summary\](.*?)$|ism','$1$3',$item['body']); if($compat) $summary = ''; if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid']) $body = fix_private_photos($body,$owner['uid'],$item,$cid); if($compat) { $compat_photos = compat_photos_list($body); } else { $compat_photos = null; } $o = "\r\n\r\n\r\n"; if(is_array($author)) { $o .= atom_render_author('author',$author); } else { $o .= atom_render_author('author',$item['author']); } $o .= atom_render_author('zot:owner',$item['owner']); 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']); // ensure it's a legal uri and not just a message-id if(! strpos($parent_item,':')) $parent_item = 'X-ZOT:' . $parent_item; $o .= '' . "\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 .= '' . xmlify($item['title']) . '' . "\r\n"; $o .= '' . xmlify(bbcode($obj['title'])) . '' . "\r\n"; $o .= '' . datetime_convert('UTC','UTC', $obj['dtstart'],'Ymd\\THis' . (($obj['adjust']) ? '\\Z' : '')) . '' . "\r\n"; $o .= '' . datetime_convert('UTC','UTC', $obj['dtend'],'Ymd\\THis' . (($obj['adjust']) ? '\\Z' : '')) . '' . "\r\n"; $o .= '' . ((is_array($obj['location'])) ? xmlify(bbcode($obj['location']['content'])) : xmlify(bbcode($obj['location']))) . '' . "\r\n"; $o .= '' . xmlify(bbcode($obj['description'])) . '' . "\r\n"; } else { $o .= '' . xmlify($item['title']) . '' . "\r\n"; if($summary) $o .= '' . xmlify(prepare_text($summary,$item['mimetype'])) . '' . "\r\n"; $o .= '' . xmlify(prepare_text($body,$item['mimetype'])) . '' . "\r\n"; } $o .= '' . xmlify($item['mid']) . '' . "\r\n"; $o .= '' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '' . "\r\n"; $o .= '' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '' . "\r\n"; $o .= '' . "\r\n"; if($item['location']) { $o .= '' . xmlify($item['location']) . '' . "\r\n"; $o .= '' . xmlify($item['location']) . '' . "\r\n"; } if($item['coord']) $o .= '' . xmlify($item['coord']) . '' . "\r\n"; if(($item['item_private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid'])) $o .= '' . (($item['item_private']) ? $item['item_private'] : 1) . '' . "\r\n"; if($item['app']) $o .= '' . "\r\n"; $verb = construct_verb($item); $o .= '' . xmlify($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 .= '' . "\r\n"; } } } if($compat_photos) { foreach($compat_photos as $enc) { $o .= '' . "\r\n"; } } 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 .= '' . "\r\n"; } } $o .= '' . "\r\n"; // build array to pass to hook $x = [ 'item' => $item, 'type' => $type, 'author' => $author, 'owner' => $owner, 'comment' => $comment, 'abook_id' => $cid, 'entry' => $o ]; /** * @hooks atom_entry * * \e array \b item * * \e string \b type * * \e array \b author * * \e array \b owner * * \e string \b comment * * \e number \b abook_id * * \e string \b entry - The generated entry and what will get returned */ call_hooks('atom_entry', $x); return $x['entry']; } function get_mentions($item,$tags) { $o = ''; if(! count($tags)) return $o; foreach($tags as $x) { if($x['ttype'] == TERM_MENTION) { $o .= "\t\t" . '' . "\r\n"; $o .= "\t\t" . '' . "\r\n"; } } return $o; } /** * @brief Return atom link elements for all of our hubs. * * @return string */ function feed_hublinks() { $hub = get_config('system', 'huburl'); $hubxml = ''; if(strlen($hub)) { $hubs = explode(',', $hub); if(count($hubs)) { foreach($hubs as $h) { $h = trim($h); if(! strlen($h)) continue; $hubxml .= '' . "\n" ; } } } return $hubxml; } /** * @brief Return atom link elements for salmon endpoints * * @param string $nick * @return string */ function feed_salmonlinks($nick) { $salmon = '' . "\n" ; // old style links that status.net still needed as of 12/2010 $salmon .= ' ' . "\n" ; $salmon .= ' ' . "\n" ; return $salmon; }