*/
use Friendica\App;
+use Friendica\Core\System;
use Friendica\Core\Config;
use Friendica\Network\Probe;
+use Friendica\Util\Lock;
require_once 'include/Contact.php';
require_once 'include/threads.php';
require_once 'include/api.php';
require_once 'mod/proxy.php';
require_once 'include/xml.php';
+require_once 'include/cache.php';
/**
* @brief This class contain functions for the OStatus protocol
*
*/
class ostatus {
- const OSTATUS_DEFAULT_POLL_INTERVAL = 30; // given in minutes
- const OSTATUS_DEFAULT_POLL_TIMEFRAME = 1440; // given in minutes
- const OSTATUS_DEFAULT_POLL_TIMEFRAME_MENTIONS = 14400; // given in minutes
+
+ private static $itemlist;
+ private static $conv_list = array();
/**
* @brief Fetches author data
*
* @param object $xpath The xpath object
- * @param object $context The xml context of the author detals
+ * @param object $context The xml context of the author details
* @param array $importer user record of the importing user
* @param array $contact Called by reference, will contain the fetched contact
* @param bool $onlyfetch Only fetch the header without updating the contact entries
*
* @return array Array of author related entries for the item
*/
- private function fetchauthor($xpath, $context, $importer, &$contact, $onlyfetch) {
+ private static function fetchauthor($xpath, $context, $importer, &$contact, $onlyfetch) {
$author = array();
$author["author-link"] = $xpath->evaluate('atom:author/atom:uri/text()', $context)->item(0)->nodeValue;
$alternate = $xpath->query("atom:author/atom:link[@rel='alternate']", $context)->item(0)->attributes;
if (is_object($alternate)) {
foreach ($alternate AS $attributes) {
- if (($attributes->name == "href") AND ($attributes->textContent != "")) {
+ if (($attributes->name == "href") && ($attributes->textContent != "")) {
$author["author-link"] = $attributes->textContent;
}
}
}
-
$author["contact-id"] = $contact["id"];
- if ($author["author-link"] != "") {
+ $found = false;
+
+ if ($aliaslink != '') {
+ $condition = array("`uid` = ? AND `alias` = ? AND `network` != ?",
+ $importer["uid"], $aliaslink, NETWORK_STATUSNET);
+ $r = dba::select('contact', array(), $condition, array('limit' => 1));
+
+ if (dbm::is_result($r)) {
+ $found = true;
+ if ($r['blocked']) {
+ $r['id'] = -1;
+ }
+ $contact = $r;
+ $author["contact-id"] = $r["id"];
+ }
+ }
+
+ if (!$found && ($author["author-link"] != "")) {
if ($aliaslink == "") {
$aliaslink = $author["author-link"];
}
- $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `nurl` IN ('%s', '%s') AND `network` != '%s'",
- intval($importer["uid"]), dbesc(normalise_link($author["author-link"])),
- dbesc(normalise_link($aliaslink)), dbesc(NETWORK_STATUSNET));
+ $condition = array("`uid` = ? AND `nurl` IN (?, ?) AND `network` != ?", $importer["uid"],
+ normalise_link($author["author-link"]), normalise_link($aliaslink), NETWORK_STATUSNET);
+ $r = dba::select('contact', array(), $condition, array('limit' => 1));
if (dbm::is_result($r)) {
- $contact = $r[0];
- $author["contact-id"] = $r[0]["id"];
- $author["author-link"] = $r[0]["url"];
+ $found = true;
+ if ($r['blocked']) {
+ $r['id'] = -1;
+ }
+ $contact = $r;
+ $author["contact-id"] = $r["id"];
}
- } elseif ($addr != "") {
- // Should not happen
- $contact = dba::fetch_first("SELECT * FROM `contact` WHERE `uid` = ? AND `addr` = ? AND `network` != ?",
+ }
+
+ if (!$found && ($addr != "")) {
+ $condition = array("`uid` = ? AND `addr` = ? AND `network` != ?",
$importer["uid"], $addr, NETWORK_STATUSNET);
+ $r = dba::select('contact', array(), $condition, array('limit' => 1));
- if (dbm::is_result($contact)) {
- $author["contact-id"] = $contact["id"];
- $author["author-link"] = $contact["url"];
+ if (dbm::is_result($r)) {
+ $found = true;
+ if ($r['blocked']) {
+ $r['id'] = -1;
+ }
+ $contact = $r;
+ $author["contact-id"] = $r["id"];
}
}
$width = $attributes->textContent;
}
}
- if (($width > 0) AND ($href != "")) {
+ if ($href != "") {
$avatarlist[$width] = $href;
}
}
$author["owner-avatar"] = $author["author-avatar"];
// Only update the contacts if it is an OStatus contact
- if ($r AND !$onlyfetch AND ($contact["network"] == NETWORK_OSTATUS)) {
+ if ($r && ($r['id'] > 0) && !$onlyfetch && ($contact["network"] == NETWORK_OSTATUS)) {
+
+ // This contact is vital, so we awake it from the dead
+ unmark_for_death($contact);
// Update contact data
+ $current = $contact;
+ unset($current['name-date']);
+
// This query doesn't seem to work
// $value = $xpath->query("atom:link[@rel='salmon']", $context)->item(0)->nodeValue;
// if ($value != "")
// if ($value != "")
// $contact["poll"] = $value;
+ $contact['url'] = $author["author-link"];
+ $contact['nurl'] = normalise_link($contact['url']);
+
$value = $xpath->evaluate('atom:author/atom:uri/text()', $context)->item(0)->nodeValue;
if ($value != "")
$contact["alias"] = $value;
if ($value != "")
$contact["location"] = $value;
- if (($contact["name"] != $r[0]["name"]) OR ($contact["nick"] != $r[0]["nick"]) OR ($contact["about"] != $r[0]["about"]) OR
- ($contact["alias"] != $r[0]["alias"]) OR ($contact["location"] != $r[0]["location"])) {
-
- logger("Update contact data for contact ".$contact["id"], LOGGER_DEBUG);
+ $contact['name-date'] = datetime_convert();
- q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `alias` = '%s', `about` = '%s', `location` = '%s', `name-date` = '%s' WHERE `id` = %d",
- dbesc($contact["name"]), dbesc($contact["nick"]), dbesc($contact["alias"]),
- dbesc($contact["about"]), dbesc($contact["location"]),
- dbesc(datetime_convert()), intval($contact["id"]));
- }
+ dba::update('contact', $contact, array('id' => $contact["id"]), $current);
- if (isset($author["author-avatar"]) AND ($author["author-avatar"] != $r[0]['avatar'])) {
+ if (!empty($author["author-avatar"]) && ($author["author-avatar"] != $current['avatar'])) {
logger("Update profile picture for contact ".$contact["id"], LOGGER_DEBUG);
-
update_contact_avatar($author["author-avatar"], $importer["uid"], $contact["id"]);
}
// Ensure that we are having this contact (with uid=0)
- $cid = get_contact($author["author-link"], 0);
+ $cid = get_contact($aliaslink, 0);
if ($cid) {
+ $fields = array('url', 'nurl', 'name', 'nick', 'alias', 'about', 'location');
+ $old_contact = dba::select('contact', $fields, array('id' => $cid), array('limit' => 1));
+
// Update it with the current values
- q("UPDATE `contact` SET `url` = '%s', `name` = '%s', `nick` = '%s', `alias` = '%s',
- `about` = '%s', `location` = '%s',
- `success_update` = '%s', `last-update` = '%s'
- WHERE `id` = %d",
- dbesc($author["author-link"]), dbesc($contact["name"]), dbesc($contact["nick"]),
- dbesc($contact["alias"]), dbesc($contact["about"]), dbesc($contact["location"]),
- dbesc(datetime_convert()), dbesc(datetime_convert()), intval($cid));
+ $fields = array('url' => $author["author-link"], 'name' => $contact["name"],
+ 'nurl' => normalise_link($author["author-link"]),
+ 'nick' => $contact["nick"], 'alias' => $contact["alias"],
+ 'about' => $contact["about"], 'location' => $contact["location"],
+ 'success_update' => datetime_convert(), 'last-update' => datetime_convert());
+
+ dba::update('contact', $fields, array('id' => $cid), $old_contact);
// Update the avatar
update_contact_avatar($author["author-avatar"], 0, $cid);
link_gcontact($gcid, $contact["uid"], $contact["id"]);
}
- return($author);
+ return $author;
}
/**
*
* @param string $xml The XML
* @param array $importer user record of the importing user
- * @param $contact
- * @param array $hub Called by reference, returns the fetched hub data
+ * @param array $contact
+ * @param string $hub Called by reference, returns the fetched hub data
*/
- public static function import($xml,$importer,&$contact, &$hub) {
- /// @todo this function is too long. It has to be split in many parts
+ public static function import($xml, $importer, &$contact, &$hub) {
+ self::process($xml, $importer, $contact, $hub);
+ }
+
+ /**
+ * @brief Internal feed processing
+ *
+ * @param string $xml The XML
+ * @param array $importer user record of the importing user
+ * @param array $contact
+ * @param string $hub Called by reference, returns the fetched hub data
+ * @param boolean $stored Is the post fresh imported or from the database?
+ * @param boolean $initialize Is it the leading post so that data has to be initialized?
+ *
+ * @return boolean Could the XML be processed?
+ */
+ private static function process($xml, $importer, &$contact, &$hub, $stored = false, $initialize = true) {
+ if ($initialize) {
+ self::$itemlist = array();
+ self::$conv_list = array();
+ }
logger("Import OStatus message", LOGGER_DEBUG);
if ($xml == "") {
- return;
+ return false;
}
- //$tempfile = tempnam(get_temppath(), "import");
- //file_put_contents($tempfile, $xml);
-
$doc = new DOMDocument();
@$doc->loadXML($xml);
$xpath->registerNamespace('ostatus', NAMESPACE_OSTATUS);
$xpath->registerNamespace('statusnet', NAMESPACE_STATUSNET);
- $gub = "";
+ $hub = "";
$hub_attributes = $xpath->query("/atom:feed/atom:link[@rel='hub']")->item(0)->attributes;
if (is_object($hub_attributes)) {
foreach ($hub_attributes AS $hub_attribute) {
}
}
}
+
$header = array();
$header["uid"] = $importer["uid"];
$header["network"] = NETWORK_OSTATUS;
$header["origin"] = 0;
$header["gravity"] = GRAVITY_PARENT;
- // it could either be a received post or a post we fetched by ourselves
- // depending on that, the first node is different
$first_child = $doc->firstChild->tagName;
if ($first_child == "feed") {
$entries = $xpath->query('/atom:feed/atom:entry');
- $header["protocol"] = PROTOCOL_OSTATUS_FEED;
} else {
$entries = $xpath->query('/atom:entry');
+ }
+
+ if ($entries->length == 1) {
+ // We reformat the XML to make it better readable
+ $doc2 = new DOMDocument();
+ $doc2->loadXML($xml);
+ $doc2->preserveWhiteSpace = false;
+ $doc2->formatOutput = true;
+ $xml2 = $doc2->saveXML();
+
$header["protocol"] = PROTOCOL_OSTATUS_SALMON;
+ $header["source"] = $xml2;
+ } elseif (!$initialize) {
+ return false;
}
- $conversation = "";
- $conversationlist = array();
- $item_id = 0;
+
+ // Fetch the first author
+ $authordata = $xpath->query('//author')->item(0);
+ $author = self::fetchauthor($xpath, $authordata, $importer, $contact, $stored);
+
+ $entry = $xpath->query('/atom:entry');
// Reverse the order of the entries
$entrylist = array();
foreach ($entries AS $entry) {
$entrylist[] = $entry;
}
+
foreach (array_reverse($entrylist) AS $entry) {
+ // fetch the author
+ $authorelement = $xpath->query('/atom:entry/atom:author', $entry);
- $mention = false;
+ if ($authorelement->length == 0) {
+ $authorelement = $xpath->query('atom:author', $entry);
+ }
- // fetch the author
- if ($first_child == "feed") {
- $author = self::fetchauthor($xpath, $doc->firstChild, $importer, $contact, false);
- } else {
- $author = self::fetchauthor($xpath, $entry, $importer, $contact, false);
+ if ($authorelement->length > 0) {
+ $author = self::fetchauthor($xpath, $entry, $importer, $contact, $stored);
}
- $value = $xpath->evaluate('atom:author/poco:preferredUsername/text()', $context)->item(0)->nodeValue;
+
+ $value = $xpath->evaluate('atom:author/poco:preferredUsername/text()', $entry)->item(0)->nodeValue;
if ($value != "") {
$nickname = $value;
} else {
$nickname = $author["author-name"];
}
+
$item = array_merge($header, $author);
- // Now get the item
$item["uri"] = $xpath->query('atom:id/text()', $entry)->item(0)->nodeValue;
- $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'",
- intval($importer["uid"]), dbesc($item["uri"]));
- if (dbm::is_result($r)) {
- logger("Item with uri ".$item["uri"]." for user ".$importer["uid"]." already existed under id ".$r[0]["id"], LOGGER_DEBUG);
- continue;
- }
-
- $item["body"] = add_page_info_to_body(html2bbcode($xpath->query('atom:content/text()', $entry)->item(0)->nodeValue));
- $item["object-type"] = $xpath->query('activity:object-type/text()', $entry)->item(0)->nodeValue;
$item["verb"] = $xpath->query('activity:verb/text()', $entry)->item(0)->nodeValue;
- // Mastodon Content Warning
- if (($item["verb"] == ACTIVITY_POST) AND $xpath->evaluate('boolean(atom:summary)', $entry)) {
- $clear_text = $xpath->query('atom:summary/text()', $entry)->item(0)->nodeValue;
-
- $item["body"] = html2bbcode($clear_text) . '[spoiler]' . $item["body"] . '[/spoiler]';
+ // Delete a message
+ if (in_array($item["verb"], array('qvitter-delete-notice', ACTIVITY_DELETE, 'delete'))) {
+ self::deleteNotice($item);
+ continue;
}
- if (($item["object-type"] == ACTIVITY_OBJ_BOOKMARK) OR ($item["object-type"] == ACTIVITY_OBJ_EVENT)) {
- $item["title"] = $xpath->query('atom:title/text()', $entry)->item(0)->nodeValue;
- $item["body"] = $xpath->query('atom:summary/text()', $entry)->item(0)->nodeValue;
- } elseif ($item["object-type"] == ACTIVITY_OBJ_QUESTION) {
- $item["title"] = $xpath->query('atom:title/text()', $entry)->item(0)->nodeValue;
+ if (in_array($item["verb"], array(NAMESPACE_OSTATUS."/unfavorite", ACTIVITY_UNFAVORITE))) {
+ // Ignore "Unfavorite" message
+ logger("Ignore unfavorite message ".print_r($item, true), LOGGER_DEBUG);
+ continue;
}
- $item["source"] = $xml;
- /// @TODO
- /// Delete a message
- if ($item["verb"] == "qvitter-delete-notice" || $item["verb"] == ACTIVITY_DELETE) {
- // ignore "Delete" messages (by now)
- logger("Ignore delete message ".print_r($item, true));
+ // Deletions come with the same uri, so we check for duplicates after processing deletions
+ if (dba::exists('item', array('uid' => $importer["uid"], 'uri' => $item["uri"]))) {
+ logger('Post with URI '.$item["uri"].' already existed for user '.$importer["uid"].'.', LOGGER_DEBUG);
continue;
+ } else {
+ logger('Processing post with URI '.$item["uri"].' for user '.$importer["uid"].'.', LOGGER_DEBUG);
}
if ($item["verb"] == ACTIVITY_JOIN) {
// ignore "Join" messages
- logger("Ignore join message ".print_r($item, true));
+ logger("Ignore join message ".print_r($item, true), LOGGER_DEBUG);
+ continue;
+ }
+
+ if ($item["verb"] == "http://mastodon.social/schema/1.0/block") {
+ // ignore mastodon "block" messages
+ logger("Ignore block message ".print_r($item, true), LOGGER_DEBUG);
continue;
}
$item["gravity"] = GRAVITY_LIKE;
}
- if ($item["verb"] == NAMESPACE_OSTATUS."/unfavorite") {
- // Ignore "Unfavorite" message
- logger("Ignore unfavorite message ".print_r($item, true));
- continue;
- }
-
// http://activitystrea.ms/schema/1.0/rsvp-yes
if (!in_array($item["verb"], array(ACTIVITY_POST, ACTIVITY_LIKE, ACTIVITY_SHARE))) {
- logger("Unhandled verb ".$item["verb"]." ".print_r($item, true));
- }
- $item["created"] = $xpath->query('atom:published/text()', $entry)->item(0)->nodeValue;
- $item["edited"] = $xpath->query('atom:updated/text()', $entry)->item(0)->nodeValue;
- $conversation = $xpath->query('ostatus:conversation/text()', $entry)->item(0)->nodeValue;
- $item['conversation-uri'] = $conversation;
-
- $conv = $xpath->query('ostatus:conversation', $entry);
- if (is_object($conv->item(0))) {
- foreach ($conv->item(0)->attributes AS $attributes) {
- if ($attributes->name == "ref") {
- $item['conversation-uri'] = $attributes->textContent;
- }
- if ($attributes->name == "href") {
- $item['conversation-href'] = $attributes->textContent;
- }
- }
+ logger("Unhandled verb ".$item["verb"]." ".print_r($item, true), LOGGER_DEBUG);
}
- $related = "";
+ self::processPost($xpath, $entry, $item, $importer);
- $inreplyto = $xpath->query('thr:in-reply-to', $entry);
- if (is_object($inreplyto->item(0))) {
- foreach ($inreplyto->item(0)->attributes AS $attributes) {
- if ($attributes->name == "ref") {
- $item["parent-uri"] = $attributes->textContent;
+ if ($initialize && (count(self::$itemlist) > 0)) {
+ if (self::$itemlist[0]['uri'] == self::$itemlist[0]['parent-uri']) {
+ // We will import it everytime, when it is started by our contacts
+ $valid = !empty(self::$itemlist[0]['contact-id']);
+ if (!$valid) {
+ // If not, then it depends on this setting
+ $valid = !Config::get('system','ostatus_full_threads');
}
- if ($attributes->name == "href") {
- $related = $attributes->textContent;
- }
- }
- }
-
- $georsspoint = $xpath->query('georss:point', $entry);
- if ($georsspoint) {
- $item["coord"] = $georsspoint->item(0)->nodeValue;
- }
- $categories = $xpath->query('atom:category', $entry);
- if ($categories) {
- foreach ($categories AS $category) {
- foreach ($category->attributes AS $attributes) {
- if ($attributes->name == "term") {
- $term = $attributes->textContent;
- if (strlen($item["tag"])) {
- $item["tag"] .= ',';
+ if ($valid) {
+ // Never post a thread when the only interaction by our contact was a like
+ $valid = false;
+ $verbs = array(ACTIVITY_POST, ACTIVITY_SHARE);
+ foreach (self::$itemlist AS $item) {
+ if (!empty($item['contact-id']) && in_array($item['verb'], $verbs)) {
+ $valid = true;
}
- $item["tag"] .= "#[url=".App::get_baseurl()."/search?tag=".$term."]".$term."[/url]";
}
}
+ } else {
+ // But we will only import complete threads
+ $valid = dba::exists('item', array('uid' => $importer["uid"], 'uri' => self::$itemlist[0]['parent-uri']));
}
- }
-
- $self = "";
- $enclosure = "";
- $links = $xpath->query('atom:link', $entry);
- if ($links) {
- foreach ($links AS $link) {
- $attribute = self::read_attributes($link);
-
- if (($attribute['rel'] != "") AND ($attribute['href'] != "")) {
- switch ($attribute['rel']) {
- case "alternate":
- $item["plink"] = $attribute['href'];
- if (($item["object-type"] == ACTIVITY_OBJ_QUESTION) OR
- ($item["object-type"] == ACTIVITY_OBJ_EVENT)) {
- $item["body"] .= add_page_info($attribute['href']);
- }
- break;
- case "ostatus:conversation":
- $conversation = $attribute['href'];
- $item['conversation-href'] = $conversation;
- if (!isset($item['conversation-uri'])) {
- $item['conversation-uri'] = $item['conversation-href'];
- }
- break;
- case "enclosure":
- $enclosure = $attribute['href'];
- if (strlen($item["attach"])) {
- $item["attach"] .= ',';
- }
- if (!isset($attribute['length'])) {
- $attribute['length'] = "0";
- }
- $item["attach"] .= '[attach]href="'.$attribute['href'].'" length="'.$attribute['length'].'" type="'.$attribute['type'].'" title="'.$attribute['title'].'"[/attach]';
- break;
- case "related":
- if ($item["object-type"] != ACTIVITY_OBJ_BOOKMARK) {
- if (!isset($item["parent-uri"])) {
- $item["parent-uri"] = $attribute['href'];
- }
- if ($related == "") {
- $related = $attribute['href'];
- }
- } else {
- $item["body"] .= add_page_info($attribute['href']);
- }
- break;
- case "self":
- $self = $attribute['href'];
- break;
- case "mentioned":
- // Notification check
- if ($importer["nurl"] == normalise_link($attribute['href'])) {
- $mention = true;
- }
- break;
+ if ($valid) {
+ $default_contact = 0;
+ $key = count(self::$itemlist);
+ for ($key = count(self::$itemlist) - 1; $key >= 0; $key--) {
+ if (empty(self::$itemlist[$key]['contact-id'])) {
+ self::$itemlist[$key]['contact-id'] = $default_contact;
+ } else {
+ $default_contact = $item['contact-id'];
}
}
- }
- }
-
- $local_id = "";
- $repeat_of = "";
-
- $notice_info = $xpath->query('statusnet:notice_info', $entry);
- if ($notice_info AND ($notice_info->length > 0)) {
- foreach ($notice_info->item(0)->attributes AS $attributes) {
- if ($attributes->name == "source") {
- $item["app"] = strip_tags($attributes->textContent);
- }
- if ($attributes->name == "local_id") {
- $local_id = $attributes->textContent;
- }
- if ($attributes->name == "repeat_of") {
- $repeat_of = $attributes->textContent;
+ foreach (self::$itemlist AS $item) {
+ $found = dba::exists('item', array('uid' => $importer["uid"], 'uri' => $item["uri"]));
+ if ($found) {
+ logger("Item with uri ".$item["uri"]." for user ".$importer["uid"]." already exists.", LOGGER_DEBUG);
+ } elseif ($item['contact-id'] < 0) {
+ logger("Item with uri ".$item["uri"]." is from a blocked contact.", LOGGER_DEBUG);
+ } else {
+ // We are having duplicated entries. Hopefully this solves it.
+ if (Lock::set('ostatus_process_item_store')) {
+ $ret = item_store($item);
+ Lock::remove('ostatus_process_item_store');
+ logger("Item with uri ".$item["uri"]." for user ".$importer["uid"].' stored. Return value: '.$ret);
+ } else {
+ $ret = item_store($item);
+ logger("We couldn't lock - but tried to store the item anyway. Return value is ".$ret);
+ }
+ }
}
}
+ self::$itemlist = array();
}
+ logger('Processing done for post with URI '.$item["uri"].' for user '.$importer["uid"].'.', LOGGER_DEBUG);
+ }
+ return true;
+ }
- // Is it a repeated post?
- if (($repeat_of != "") OR ($item["verb"] == ACTIVITY_SHARE)) {
- $activityobjects = $xpath->query('activity:object', $entry)->item(0);
+ private static function deleteNotice($item) {
- if (is_object($activityobjects)) {
+ $condition = array('uid' => $item['uid'], 'author-link' => $item['author-link'], 'uri' => $item['uri']);
+ $deleted = dba::select('item', array('id', 'parent-uri'), $condition, array('limit' => 1));
+ if (!dbm::is_result($deleted)) {
+ logger('Item from '.$item['author-link'].' with uri '.$item['uri'].' for user '.$item['uid']." wasn't found. We don't delete it. ");
+ return;
+ }
- $orig_uri = $xpath->query("activity:object/atom:id", $activityobjects)->item(0)->nodeValue;
- if (!isset($orig_uri)) {
- $orig_uri = $xpath->query('atom:id/text()', $activityobjects)->item(0)->nodeValue;
- }
- $orig_links = $xpath->query("activity:object/atom:link[@rel='alternate']", $activityobjects);
- if ($orig_links AND ($orig_links->length > 0)) {
- foreach ($orig_links->item(0)->attributes AS $attributes) {
- if ($attributes->name == "href") {
- $orig_link = $attributes->textContent;
- }
- }
- }
- if (!isset($orig_link)) {
- $orig_link = $xpath->query("atom:link[@rel='alternate']", $activityobjects)->item(0)->nodeValue;
- }
- if (!isset($orig_link)) {
- $orig_link = self::convert_href($orig_uri);
- }
- $orig_body = $xpath->query('activity:object/atom:content/text()', $activityobjects)->item(0)->nodeValue;
- if (!isset($orig_body)) {
- $orig_body = $xpath->query('atom:content/text()', $activityobjects)->item(0)->nodeValue;
- }
- $orig_created = $xpath->query('atom:published/text()', $activityobjects)->item(0)->nodeValue;
- $orig_edited = $xpath->query('atom:updated/text()', $activityobjects)->item(0)->nodeValue;
+ // Currently we don't have a central deletion function that we could use in this case. The function "item_drop" doesn't work for that case
+ dba::update('item', array('deleted' => true, 'title' => '', 'body' => '',
+ 'edited' => datetime_convert(), 'changed' => datetime_convert()),
+ array('id' => $deleted["id"]));
- $orig_contact = $contact;
- $orig_author = self::fetchauthor($xpath, $activityobjects, $importer, $orig_contact, false);
+ delete_thread($deleted["id"], $deleted["parent-uri"]);
- $item["author-name"] = $orig_author["author-name"];
- $item["author-link"] = $orig_author["author-link"];
- $item["author-avatar"] = $orig_author["author-avatar"];
+ logger('Deleted item with uri '.$item['uri'].' for user '.$item['uid']);
+ }
- $item["body"] = add_page_info_to_body(html2bbcode($orig_body));
- $item["created"] = $orig_created;
- $item["edited"] = $orig_edited;
+ /**
+ * @brief Processes the XML for a post
+ *
+ * @param object $xpath The xpath object
+ * @param object $entry The xml entry that is processed
+ * @param array $item The item array
+ * @param array $importer user record of the importing user
+ */
+ private static function processPost($xpath, $entry, &$item, $importer) {
+ $item["body"] = html2bbcode($xpath->query('atom:content/text()', $entry)->item(0)->nodeValue);
+ $item["object-type"] = $xpath->query('activity:object-type/text()', $entry)->item(0)->nodeValue;
+ if (($item["object-type"] == ACTIVITY_OBJ_BOOKMARK) || ($item["object-type"] == ACTIVITY_OBJ_EVENT)) {
+ $item["title"] = $xpath->query('atom:title/text()', $entry)->item(0)->nodeValue;
+ $item["body"] = $xpath->query('atom:summary/text()', $entry)->item(0)->nodeValue;
+ } elseif ($item["object-type"] == ACTIVITY_OBJ_QUESTION) {
+ $item["title"] = $xpath->query('atom:title/text()', $entry)->item(0)->nodeValue;
+ }
- $item["uri"] = $orig_uri;
+ $item["created"] = $xpath->query('atom:published/text()', $entry)->item(0)->nodeValue;
+ $item["edited"] = $xpath->query('atom:updated/text()', $entry)->item(0)->nodeValue;
+ $conversation = $xpath->query('ostatus:conversation/text()', $entry)->item(0)->nodeValue;
+ $item['conversation-uri'] = $conversation;
- if (!isset($item["plink"])) {
- $item["plink"] = $orig_link;
- }
+ $conv = $xpath->query('ostatus:conversation', $entry);
+ if (is_object($conv->item(0))) {
+ foreach ($conv->item(0)->attributes AS $attributes) {
+ if ($attributes->name == "ref") {
+ $item['conversation-uri'] = $attributes->textContent;
+ }
+ if ($attributes->name == "href") {
+ $item['conversation-href'] = $attributes->textContent;
+ }
+ }
+ }
- $item["verb"] = $xpath->query('activity:verb/text()', $activityobjects)->item(0)->nodeValue;
+ $related = "";
- $item["object-type"] = $xpath->query('activity:object/activity:object-type/text()', $activityobjects)->item(0)->nodeValue;
- if (!isset($item["object-type"])) {
- $item["object-type"] = $xpath->query('activity:object-type/text()', $activityobjects)->item(0)->nodeValue;
- }
+ $inreplyto = $xpath->query('thr:in-reply-to', $entry);
+ if (is_object($inreplyto->item(0))) {
+ foreach ($inreplyto->item(0)->attributes AS $attributes) {
+ if ($attributes->name == "ref") {
+ $item["parent-uri"] = $attributes->textContent;
+ }
+ if ($attributes->name == "href") {
+ $related = $attributes->textContent;
+ }
+ }
+ }
- $enclosures = $xpath->query("atom:link[@rel='alternate']", $activityobjects);
- if ($enclosures) {
- foreach ($enclosures AS $link) {
- $attribute = self::read_attributes($link);
- if ($href != "") {
- $enclosure = $attribute['href'];
- if (strlen($item["attach"])) {
- $item["attach"] .= ',';
- }
- if (!isset($attribute['length'])) {
- $attribute['length'] = "0";
- }
- $item["attach"] .= '[attach]href="'.$attribute['href'].'" length="'.$attribute['length'].'" type="'.$attribute['type'].'" title="'.$attribute['title'].'"[/attach]';
- }
+ $georsspoint = $xpath->query('georss:point', $entry);
+ if (!empty($georsspoint) && ($georsspoint->length > 0)) {
+ $item["coord"] = $georsspoint->item(0)->nodeValue;
+ }
+
+ $categories = $xpath->query('atom:category', $entry);
+ if ($categories) {
+ foreach ($categories AS $category) {
+ foreach ($category->attributes AS $attributes) {
+ if ($attributes->name == "term") {
+ $term = $attributes->textContent;
+ if (strlen($item["tag"])) {
+ $item["tag"] .= ',';
}
+ $item["tag"] .= "#[url=".System::baseUrl()."/search?tag=".$term."]".$term."[/url]";
}
}
}
+ }
- //if ($enclosure != "")
- // $item["body"] .= add_page_info($enclosure);
-
- if (isset($item["parent-uri"])) {
- $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'",
- intval($importer["uid"]), dbesc($item["parent-uri"]));
-
- // Only fetch missing stuff if it is a comment or reshare.
- if (in_array($item["verb"], array(ACTIVITY_POST, ACTIVITY_SHARE)) AND
- !dbm::is_result($r) AND ($related != "")) {
- $reply_path = str_replace("/notice/", "/api/statuses/show/", $related).".atom";
+ $self = '';
+ $add_body = '';
- if ($reply_path != $related) {
- logger("Fetching related items for user ".$importer["uid"]." from ".$reply_path, LOGGER_DEBUG);
- $reply_xml = fetch_url($reply_path);
+ $links = $xpath->query('atom:link', $entry);
+ if ($links) {
+ $link_data = self::processLinks($links, $item);
+ $self = $link_data['self'];
+ $add_body = $link_data['add_body'];
+ }
- $reply_contact = $contact;
- self::import($reply_xml,$importer,$reply_contact, $reply_hub);
+ $repeat_of = "";
- // After the import try to fetch the parent item again
- $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'",
- intval($importer["uid"]), dbesc($item["parent-uri"]));
- }
+ $notice_info = $xpath->query('statusnet:notice_info', $entry);
+ if ($notice_info && ($notice_info->length > 0)) {
+ foreach ($notice_info->item(0)->attributes AS $attributes) {
+ if ($attributes->name == "source") {
+ $item["app"] = strip_tags($attributes->textContent);
}
- if (dbm::is_result($r)) {
- $item["type"] = 'remote-comment';
- $item["gravity"] = GRAVITY_COMMENT;
+ if ($attributes->name == "repeat_of") {
+ $repeat_of = $attributes->textContent;
}
- } else {
- $item["parent-uri"] = $item["uri"];
}
- $item_id = self::completion($conversation, $importer["uid"], $item, $self);
-
- if (!$item_id) {
- logger("Error storing item", LOGGER_DEBUG);
- continue;
+ }
+ // Is it a repeated post?
+ if (($repeat_of != "") || ($item["verb"] == ACTIVITY_SHARE)) {
+ $link_data = self::processRepeatedItem($xpath, $entry, $item, $importer);
+ if (!empty($link_data['add_body'])) {
+ $add_body .= $link_data['add_body'];
}
-
- logger("Item was stored with id ".$item_id, LOGGER_DEBUG);
}
- }
-
- /**
- * @brief Create an url out of an uri
- *
- * @param string $href URI in the format "parameter1:parameter1:..."
- *
- * @return string URL in the format http(s)://....
- */
- public static function convert_href($href) {
- $elements = explode(":",$href);
-
- if ((count($elements) <= 2) OR ($elements[0] != "tag"))
- return $href;
-
- $server = explode(",", $elements[1]);
- $conversation = explode("=", $elements[2]);
-
- if ((count($elements) == 4) AND ($elements[2] == "post"))
- return "http://".$server[0]."/notice/".$elements[3];
-
- if ((count($conversation) != 2) OR ($conversation[1] ==""))
- return $href;
- if ($elements[3] == "objectType=thread")
- return "http://".$server[0]."/conversation/".$conversation[1];
- else
- return "http://".$server[0]."/notice/".$conversation[1];
+ $item["body"] .= $add_body;
- return $href;
- }
+ // Only add additional data when there is no picture in the post
+ if (!strstr($item["body"],'[/img]')) {
+ $item["body"] = add_page_info_to_body($item["body"]);
+ }
- /**
- * @brief Checks if there are entries in conversations that aren't present on our side
- *
- * @param bool $mentions Fetch conversations where we are mentioned
- * @param bool $override Override the interval setting
- */
- public static function check_conversations($mentions = false, $override = false) {
- $last = get_config('system','ostatus_last_poll');
+ // Mastodon Content Warning
+ if (($item["verb"] == ACTIVITY_POST) && $xpath->evaluate('boolean(atom:summary)', $entry)) {
+ $clear_text = $xpath->query('atom:summary/text()', $entry)->item(0)->nodeValue;
- $poll_interval = intval(get_config('system','ostatus_poll_interval'));
- if (!$poll_interval) {
- $poll_interval = self::OSTATUS_DEFAULT_POLL_INTERVAL;
+ $item["body"] = html2bbcode($clear_text) . '[spoiler]' . $item["body"] . '[/spoiler]';
}
- // Don't poll if the interval is set negative
- if (($poll_interval < 0) AND !$override) {
- return;
+ if (($self != '') && empty($item['protocol'])) {
+ self::fetchSelf($self, $item);
}
- if (!$mentions) {
- $poll_timeframe = intval(get_config('system','ostatus_poll_timeframe'));
- if (!$poll_timeframe) {
- $poll_timeframe = self::OSTATUS_DEFAULT_POLL_TIMEFRAME;
- }
- } else {
- $poll_timeframe = intval(get_config('system','ostatus_poll_timeframe'));
- if (!$poll_timeframe) {
- $poll_timeframe = self::OSTATUS_DEFAULT_POLL_TIMEFRAME_MENTIONS;
- }
+ if (!empty($item["conversation-href"])) {
+ self::fetchConversation($item['conversation-href'], $item['conversation-uri']);
}
-
- if ($last AND !$override) {
- $next = $last + ($poll_interval * 60);
- if ($next > time()) {
- logger('poll interval not reached');
- return;
+ if (isset($item["parent-uri"]) && ($related != '')) {
+ if (!dba::exists('item', array('uid' => $importer["uid"], 'uri' => $item['parent-uri']))) {
+ self::fetchRelated($related, $item["parent-uri"], $importer);
+ } else {
+ logger('Reply with URI '.$item["uri"].' already existed for user '.$importer["uid"].'.', LOGGER_DEBUG);
}
- }
- logger('cron_start');
-
- $start = date("Y-m-d H:i:s", time() - ($poll_timeframe * 60));
-
- if ($mentions) {
- $conversations = q("SELECT `term`.`oid`, `term`.`url`, `term`.`uid` FROM `term`
- STRAIGHT_JOIN `thread` ON `thread`.`iid` = `term`.`oid` AND `thread`.`uid` = `term`.`uid`
- WHERE `term`.`type` = 7 AND `term`.`term` > '%s' AND `thread`.`mention`
- GROUP BY `term`.`url`, `term`.`uid`, `term`.`oid`, `term`.`term` ORDER BY `term`.`term` DESC", dbesc($start));
+ $item["type"] = 'remote-comment';
+ $item["gravity"] = GRAVITY_COMMENT;
} else {
- $conversations = q("SELECT `oid`, `url`, `uid` FROM `term`
- WHERE `type` = 7 AND `term` > '%s'
- GROUP BY `url`, `uid`, `oid`, `term` ORDER BY `term` DESC", dbesc($start));
+ $item["parent-uri"] = $item["uri"];
}
- foreach ($conversations AS $conversation) {
- self::completion($conversation['url'], $conversation['uid']);
+ if (($item['author-link'] != '') && !empty($item['protocol'])) {
+ $item = store_conversation($item);
}
- logger('cron_end');
-
- set_config('system','ostatus_last_poll', time());
+ self::$itemlist[] = $item;
}
/**
- * @brief Updates the gcontact table with actor data from the conversation
+ * @brief Fetch the conversation for posts
*
- * @param object $actor The actor object that contains the contact data
+ * @param string $conversation The link to the conversation
+ * @param string $conversation_uri The conversation in "uri" format
*/
- private function conv_fetch_actor($actor) {
-
- // We set the generation to "3" since the data here is not as reliable as the data we get on other occasions
- $contact = array("network" => NETWORK_OSTATUS, "generation" => 3);
-
- if (isset($actor->url))
- $contact["url"] = $actor->url;
+ private static function fetchConversation($conversation, $conversation_uri) {
- if (isset($actor->displayName))
- $contact["name"] = $actor->displayName;
-
- if (isset($actor->portablecontacts_net->displayName))
- $contact["name"] = $actor->portablecontacts_net->displayName;
-
- if (isset($actor->portablecontacts_net->preferredUsername))
- $contact["nick"] = $actor->portablecontacts_net->preferredUsername;
+ // Ensure that we only store a conversation once in a process
+ if (isset(self::$conv_list[$conversation])) {
+ return;
+ }
- if (isset($actor->id))
- $contact["alias"] = $actor->id;
+ self::$conv_list[$conversation] = true;
- if (isset($actor->summary))
- $contact["about"] = $actor->summary;
+ $conversation_data = z_fetch_url($conversation, false, $redirects, array('accept_content' => 'application/atom+xml, text/html'));
- if (isset($actor->portablecontacts_net->note))
- $contact["about"] = $actor->portablecontacts_net->note;
+ if (!$conversation_data['success']) {
+ return;
+ }
- if (isset($actor->portablecontacts_net->addresses->formatted))
- $contact["location"] = $actor->portablecontacts_net->addresses->formatted;
+ $xml = '';
+ if (stristr($conversation_data['header'], 'Content-Type: application/atom+xml')) {
+ $xml = $conversation_data['body'];
+ }
- if (isset($actor->image->url))
- $contact["photo"] = $actor->image->url;
+ if ($xml == '') {
+ $doc = new DOMDocument();
+ if (!@$doc->loadHTML($conversation_data['body'])) {
+ return;
+ }
+ $xpath = new DomXPath($doc);
- if (isset($actor->image->width))
- $avatarwidth = $actor->image->width;
+ $links = $xpath->query('//link');
+ if ($links) {
+ foreach ($links AS $link) {
+ $attribute = ostatus::read_attributes($link);
+ if (($attribute['rel'] == 'alternate') && ($attribute['type'] == 'application/atom+xml')) {
+ $file = $attribute['href'];
+ }
+ }
+ if ($file != '') {
+ $conversation_atom = z_fetch_url($attribute['href']);
- if (is_array($actor->status_net->avatarLinks))
- foreach ($actor->status_net->avatarLinks AS $avatar) {
- if ($avatarsize < $avatar->width) {
- $contact["photo"] = $avatar->url;
- $avatarsize = $avatar->width;
+ if ($conversation_atom['success']) {
+ $xml = $conversation_atom['body'];
+ }
}
}
+ }
- $contact["hide"] = false; // OStatus contacts are never hidden
- update_gcontact($contact);
+ if ($xml == '') {
+ return;
+ }
+
+ self::storeConversation($xml, $conversation, $conversation_uri);
}
/**
- * @brief Fetches the conversation url for a given item link or conversation id
- *
- * @param string $self The link to the posting
- * @param string $conversation_id The conversation id
+ * @brief Store a feed in several conversation entries
*
- * @return string The conversation url
+ * @param string $xml The feed
*/
- private function fetch_conversation($self, $conversation_id = "") {
-
- if ($conversation_id != "") {
- $elements = explode(":", $conversation_id);
-
- if ((count($elements) <= 2) OR ($elements[0] != "tag"))
- return $conversation_id;
- }
-
- if ($self == "")
- return "";
-
- $json = str_replace(".atom", ".json", $self);
+ private static function storeConversation($xml, $conversation = '', $conversation_uri = '') {
+ $doc = new DOMDocument();
+ @$doc->loadXML($xml);
- $raw = fetch_url($json);
- if ($raw == "")
- return "";
+ $xpath = new DomXPath($doc);
+ $xpath->registerNamespace('atom', NAMESPACE_ATOM1);
+ $xpath->registerNamespace('thr', NAMESPACE_THREAD);
+ $xpath->registerNamespace('ostatus', NAMESPACE_OSTATUS);
- $data = json_decode($raw);
- if (!is_object($data))
- return "";
+ $entries = $xpath->query('/atom:feed/atom:entry');
- $conversation_id = $data->statusnet_conversation_id;
+ // Now store the entries
+ foreach ($entries AS $entry) {
+ $doc2 = new DOMDocument();
+ $doc2->preserveWhiteSpace = false;
+ $doc2->formatOutput = true;
- $pos = strpos($self, "/api/statuses/show/");
- $base_url = substr($self, 0, $pos);
+ $conv_data = array();
- return $base_url."/conversation/".$conversation_id;
- }
+ $conv_data['protocol'] = PROTOCOL_SPLITTED_CONV;
+ $conv_data['network'] = NETWORK_OSTATUS;
+ $conv_data['uri'] = $xpath->query('atom:id/text()', $entry)->item(0)->nodeValue;
- /**
- * @brief Fetches actor details of a given actor and user id
- *
- * @param string $actor The actor url
- * @param int $uid The user id
- * @param int $contact_id The default contact-id
- *
- * @return array Array with actor details
- */
- private function get_actor_details($actor, $uid, $contact_id) {
+ $inreplyto = $xpath->query('thr:in-reply-to', $entry);
+ if (is_object($inreplyto->item(0))) {
+ foreach ($inreplyto->item(0)->attributes AS $attributes) {
+ if ($attributes->name == "ref") {
+ $conv_data['reply-to-uri'] = $attributes->textContent;
+ }
+ }
+ }
- $details = array();
+ $conv = $xpath->query('ostatus:conversation/text()', $entry)->item(0)->nodeValue;
+ $conv_data['conversation-uri'] = $conv;
- $contact = q("SELECT `id`, `rel`, `network` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' AND `network` != '%s'",
- $uid, normalise_link($actor), NETWORK_STATUSNET);
+ $conv = $xpath->query('ostatus:conversation', $entry);
+ if (is_object($conv->item(0))) {
+ foreach ($conv->item(0)->attributes AS $attributes) {
+ if ($attributes->name == "ref") {
+ $conv_data['conversation-uri'] = $attributes->textContent;
+ }
+ if ($attributes->name == "href") {
+ $conv_data['conversation-href'] = $attributes->textContent;
+ }
+ }
+ }
- if (!$contact)
- $contact = q("SELECT `id`, `rel`, `network` FROM `contact` WHERE `uid` = %d AND `alias` IN ('%s', '%s') AND `network` != '%s'",
- $uid, $actor, normalise_link($actor), NETWORK_STATUSNET);
+ if ($conversation != '') {
+ $conv_data['conversation-uri'] = $conversation;
+ }
- if ($contact) {
- logger("Found contact for url ".$actor, LOGGER_DEBUG);
- $details["contact_id"] = $contact[0]["id"];
- $details["network"] = $contact[0]["network"];
+ if ($conversation_uri != '') {
+ $conv_data['conversation-uri'] = $conversation_uri;
+ }
- $details["not_following"] = !in_array($contact[0]["rel"], array(CONTACT_IS_SHARING, CONTACT_IS_FRIEND));
- } else {
- logger("No contact found for user ".$uid." and url ".$actor, LOGGER_DEBUG);
+ $entry = $doc2->importNode($entry, true);
- // Adding a global contact
- /// @TODO Use this data for the post
- $details["global_contact_id"] = get_contact($actor, 0);
+ $doc2->appendChild($entry);
- logger("Global contact ".$global_contact_id." found for url ".$actor, LOGGER_DEBUG);
+ $conv_data['source'] = $doc2->saveXML();
- $details["contact_id"] = $contact_id;
- $details["network"] = NETWORK_OSTATUS;
+ $condition = array('item-uri' => $conv_data['uri'],'protocol' => PROTOCOL_OSTATUS_FEED);
+ if (dba::exists('conversation', $condition)) {
+ logger('Delete deprecated entry for URI '.$conv_data['uri'], LOGGER_DEBUG);
+ dba::delete('conversation', array('item-uri' => $conv_data['uri']));
+ }
- $details["not_following"] = true;
+ logger('Store conversation data for uri '.$conv_data['uri'], LOGGER_DEBUG);
+ store_conversation($conv_data);
}
-
- return $details;
}
/**
- * @brief Stores an item and completes the thread
+ * @brief Fetch the own post so that it can be stored later
+ * @param array $item The item array
*
- * @param string $conversation_url The URI of the conversation
- * @param integer $uid The user id
- * @param array $item Data of the item that is to be posted
+ * We want to store the original data for later processing.
+ * This function is meant for cases where we process a feed with multiple entries.
+ * In that case we need to fetch the single posts here.
*
- * @return integer The item id of the posted item array
+ * @param string $self The link to the self item
*/
- private function completion($conversation_url, $uid, $item = array(), $self = "") {
-
- /// @todo This function is totally ugly and has to be rewritten totally
-
- // Import all threads or only threads that were started by our followers?
- $all_threads = !get_config('system','ostatus_full_threads');
-
- $item_stored = -1;
-
- $conversation_url = self::fetch_conversation($self, $conversation_url);
-
- // If the thread shouldn't be completed then store the item and go away
- // Don't do a completion on liked content
- if (((intval(get_config('system','ostatus_poll_interval')) == -2) AND (count($item) > 0)) OR
- ($item["verb"] == ACTIVITY_LIKE) OR ($conversation_url == "")) {
- $item_stored = item_store($item, $all_threads);
- return $item_stored;
- } elseif (count($item) > 0) {
- $item = store_conversation($item);
+ private static function fetchSelf($self, &$item) {
+ $condition = array('`item-uri` = ? AND `protocol` IN (?, ?)', $self, PROTOCOL_DFRN, PROTOCOL_OSTATUS_SALMON);
+ if (dba::exists('conversation', $condition)) {
+ logger('Conversation '.$item['uri'].' is already stored.', LOGGER_DEBUG);
+ return;
}
- // Get the parent
- $parents = q("SELECT `item`.`id`, `item`.`parent`, `item`.`uri`, `item`.`contact-id`, `item`.`type`,
- `item`.`verb`, `item`.`visible` FROM `term`
- STRAIGHT_JOIN `item` AS `thritem` ON `thritem`.`parent` = `term`.`oid`
- STRAIGHT_JOIN `item` ON `item`.`parent` = `thritem`.`parent`
- WHERE `term`.`uid` = %d AND `term`.`otype` = %d AND `term`.`type` = %d AND `term`.`url` = '%s'",
- intval($uid), intval(TERM_OBJ_POST), intval(TERM_CONVERSATION), dbesc($conversation_url));
-
-/* 2016-10-23: The old query will be kept until we are sure that the query above is a good and fast replacement
-
- $parents = q("SELECT `id`, `parent`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `id` IN
- (SELECT `parent` FROM `item` WHERE `id` IN
- (SELECT `oid` FROM `term` WHERE `uid` = %d AND `otype` = %d AND `type` = %d AND `url` = '%s'))",
- intval($uid), intval(TERM_OBJ_POST), intval(TERM_CONVERSATION), dbesc($conversation_url));
-*/
- if ($parents)
- $parent = $parents[0];
- elseif (count($item) > 0) {
- $parent = $item;
- $parent["type"] = "remote";
- $parent["verb"] = ACTIVITY_POST;
- $parent["visible"] = 1;
- } else {
- // Preset the parent
- $r = q("SELECT `id` FROM `contact` WHERE `self` AND `uid`=%d", $uid);
- if (!$r)
- return(-2);
-
- $parent = array();
- $parent["id"] = 0;
- $parent["parent"] = 0;
- $parent["uri"] = "";
- $parent["contact-id"] = $r[0]["id"];
- $parent["type"] = "remote";
- $parent["verb"] = ACTIVITY_POST;
- $parent["visible"] = 1;
- }
-
- $conv = str_replace("/conversation/", "/api/statusnet/conversation/", $conversation_url).".as";
- $pageno = 1;
- $items = array();
-
- logger('fetching conversation url '.$conv.' (Self: '.$self.') for user '.$uid);
-
- do {
- $conv_arr = z_fetch_url($conv."?page=".$pageno);
-
- // If it is a non-ssl site and there is an error, then try ssl or vice versa
- if (!$conv_arr["success"] AND (substr($conv, 0, 7) == "http://")) {
- $conv = str_replace("http://", "https://", $conv);
- $conv_as = fetch_url($conv."?page=".$pageno);
- } elseif (!$conv_arr["success"] AND (substr($conv, 0, 8) == "https://")) {
- $conv = str_replace("https://", "http://", $conv);
- $conv_as = fetch_url($conv."?page=".$pageno);
- } else
- $conv_as = $conv_arr["body"];
-
- $conv_as = str_replace(',"statusnet:notice_info":', ',"statusnet_notice_info":', $conv_as);
- $conv_as = json_decode($conv_as);
-
- $no_of_items = sizeof($items);
-
- if (@is_array($conv_as->items))
- foreach ($conv_as->items AS $single_item)
- $items[$single_item->id] = $single_item;
-
- if ($no_of_items == sizeof($items))
- break;
-
- $pageno++;
-
- } while (true);
-
- logger('fetching conversation done. Found '.count($items).' items');
+ $self_data = z_fetch_url($self);
- if (!sizeof($items)) {
- if (count($item) > 0) {
- $item_stored = item_store($item, $all_threads);
-
- if ($item_stored) {
- logger("Conversation ".$conversation_url." couldn't be fetched. Item uri ".$item["uri"]." stored: ".$item_stored, LOGGER_DEBUG);
- self::store_conversation($item_id, $conversation_url);
- }
-
- return($item_stored);
- } else
- return(-3);
+ if (!$self_data['success']) {
+ return;
}
- $items = array_reverse($items);
-
- $r = q("SELECT `nurl` FROM `contact` WHERE `uid` = %d AND `self`", intval($uid));
- $importer = $r[0];
-
- $new_parent = true;
-
- foreach ($items as $single_conv) {
-
- // Update the gcontact table
- self::conv_fetch_actor($single_conv->actor);
-
- // Test - remove before flight
- //$tempfile = tempnam(get_temppath(), "conversation");
- //file_put_contents($tempfile, json_encode($single_conv));
-
- $mention = false;
-
- if (isset($single_conv->object->id))
- $single_conv->id = $single_conv->object->id;
-
- $plink = self::convert_href($single_conv->id);
- if (isset($single_conv->object->url))
- $plink = self::convert_href($single_conv->object->url);
-
- if (@!$single_conv->id)
- continue;
-
- logger("Got id ".$single_conv->id, LOGGER_DEBUG);
-
- if ($first_id == "") {
- $first_id = $single_conv->id;
-
- // The first post of the conversation isn't our first post. There are three options:
- // 1. Our conversation hasn't the "real" thread starter
- // 2. This first post is a post inside our thread
- // 3. This first post is a post inside another thread
- if (($first_id != $parent["uri"]) AND ($parent["uri"] != "")) {
-
- $new_parent = true;
+ // We reformat the XML to make it better readable
+ $doc = new DOMDocument();
+ $doc->loadXML($self_data['body']);
+ $doc->preserveWhiteSpace = false;
+ $doc->formatOutput = true;
+ $xml = $doc->saveXML();
- $new_parents = q("SELECT `id`, `parent`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `id` IN
- (SELECT `parent` FROM `item`
- WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s')) LIMIT 1",
- intval($uid), dbesc($first_id), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN));
- if ($new_parents) {
- if ($new_parents[0]["parent"] == $parent["parent"]) {
- // Option 2: This post is already present inside our thread - but not as thread starter
- logger("Option 2: uri present in our thread: ".$first_id, LOGGER_DEBUG);
- $first_id = $parent["uri"];
- } else {
- // Option 3: Not so good. We have mixed parents. We have to see how to clean this up.
- // For now just take the new parent.
- $parent = $new_parents[0];
- $first_id = $parent["uri"];
- logger("Option 3: mixed parents for uri ".$first_id, LOGGER_DEBUG);
- }
- } else {
- // Option 1: We hadn't got the real thread starter
- // We have to clean up our existing messages.
- $parent["id"] = 0;
- $parent["uri"] = $first_id;
- logger("Option 1: we have a new parent: ".$first_id, LOGGER_DEBUG);
- }
- } elseif ($parent["uri"] == "") {
- $parent["id"] = 0;
- $parent["uri"] = $first_id;
- }
- }
+ $item["protocol"] = PROTOCOL_OSTATUS_SALMON;
+ $item["source"] = $xml;
- $parent_uri = $parent["uri"];
+ logger('Conversation '.$item['uri'].' is now fetched.', LOGGER_DEBUG);
+ }
- // "context" only seems to exist on older servers
- if (isset($single_conv->context->inReplyTo->id)) {
- $parent_exists = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s') LIMIT 1",
- intval($uid), dbesc($single_conv->context->inReplyTo->id), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN));
- if ($parent_exists)
- $parent_uri = $single_conv->context->inReplyTo->id;
+ /**
+ * @brief Fetch related posts and processes them
+ *
+ * @param string $related The link to the related item
+ * @param string $related_uri The related item in "uri" format
+ * @param array $importer user record of the importing user
+ */
+ private static function fetchRelated($related, $related_uri, $importer) {
+ $condition = array('`item-uri` = ? AND `protocol` IN (?, ?)', $related_uri, PROTOCOL_DFRN, PROTOCOL_OSTATUS_SALMON);
+ $conversation = dba::select('conversation', array('source', 'protocol'), $condition, array('limit' => 1));
+ if (dbm::is_result($conversation)) {
+ $stored = true;
+ $xml = $conversation['source'];
+ if (self::process($xml, $importer, $contact, $hub, $stored, false)) {
+ logger('Got valid cached XML for URI '.$related_uri, LOGGER_DEBUG);
+ return;
}
-
- // This is the current way
- if (isset($single_conv->object->inReplyTo->id)) {
- $parent_exists = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s') LIMIT 1",
- intval($uid), dbesc($single_conv->object->inReplyTo->id), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN));
- if ($parent_exists)
- $parent_uri = $single_conv->object->inReplyTo->id;
+ if ($conversation['protocol'] == PROTOCOL_OSTATUS_SALMON) {
+ logger('Delete invalid cached XML for URI '.$related_uri, LOGGER_DEBUG);
+ dba::delete('conversation', array('item-uri' => $related_uri));
}
+ }
- $message_exists = q("SELECT `id`, `parent`, `uri` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s') LIMIT 1",
- intval($uid), dbesc($single_conv->id),
- dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN));
- if ($message_exists) {
- logger("Message ".$single_conv->id." already existed on the system", LOGGER_DEBUG);
-
- if ($parent["id"] != 0) {
- $existing_message = $message_exists[0];
+ $stored = false;
+ $related_data = z_fetch_url($related, false, $redirects, array('accept_content' => 'application/atom+xml, text/html'));
- // We improved the way we fetch OStatus messages, this shouldn't happen very often now
- /// @TODO We have to change the shadow copies as well. This way here is really ugly.
- if ($existing_message["parent"] != $parent["id"]) {
- logger('updating id '.$existing_message["id"].' with parent '.$existing_message["parent"].' to parent '.$parent["id"].' uri '.$parent["uri"].' thread '.$parent_uri, LOGGER_DEBUG);
+ if (!$related_data['success']) {
+ return;
+ }
- // Update the parent id of the selected item
- $r = q("UPDATE `item` SET `parent` = %d, `parent-uri` = '%s' WHERE `id` = %d",
- intval($parent["id"]), dbesc($parent["uri"]), intval($existing_message["id"]));
+ $xml = '';
- // Update the parent uri in the thread - but only if it points to itself
- $r = q("UPDATE `item` SET `thr-parent` = '%s' WHERE `id` = %d AND `uri` = `thr-parent`",
- dbesc($parent_uri), intval($existing_message["id"]));
+ if (stristr($related_data['header'], 'Content-Type: application/atom+xml')) {
+ logger('Directly fetched XML for URI '.$related_uri, LOGGER_DEBUG);
+ $xml = $related_data['body'];
+ }
- // try to change all items of the same parent
- $r = q("UPDATE `item` SET `parent` = %d, `parent-uri` = '%s' WHERE `parent` = %d",
- intval($parent["id"]), dbesc($parent["uri"]), intval($existing_message["parent"]));
+ if ($xml == '') {
+ $doc = new DOMDocument();
+ if (!@$doc->loadHTML($related_data['body'])) {
+ return;
+ }
+ $xpath = new DomXPath($doc);
- // Update the parent uri in the thread - but only if it points to itself
- $r = q("UPDATE `item` SET `thr-parent` = '%s' WHERE (`parent` = %d) AND (`uri` = `thr-parent`)",
- dbesc($parent["uri"]), intval($existing_message["parent"]));
+ $atom_file = '';
- // Now delete the thread
- delete_thread($existing_message["parent"]);
+ $links = $xpath->query('//link');
+ if ($links) {
+ foreach ($links AS $link) {
+ $attribute = self::read_attributes($link);
+ if (($attribute['rel'] == 'alternate') && ($attribute['type'] == 'application/atom+xml')) {
+ $atom_file = $attribute['href'];
}
}
+ if ($atom_file != '') {
+ $related_atom = z_fetch_url($atom_file);
- // The item we are having on the system is the one that we wanted to store via the item array
- if (isset($item["uri"]) AND ($item["uri"] == $existing_message["uri"])) {
- $item = array();
- $item_stored = 0;
+ if ($related_atom['success']) {
+ logger('Fetched XML for URI '.$related_uri, LOGGER_DEBUG);
+ $xml = $related_atom['body'];
+ }
}
-
- continue;
}
+ }
- if (is_array($single_conv->to))
- foreach($single_conv->to AS $to)
- if ($importer["nurl"] == normalise_link($to->id))
- $mention = true;
+ // Workaround for older GNU Social servers
+ if (($xml == '') && strstr($related, '/notice/')) {
+ $related_atom = z_fetch_url(str_replace('/notice/', '/api/statuses/show/', $related).'.atom');
- $actor = $single_conv->actor->id;
- if (isset($single_conv->actor->url))
- $actor = $single_conv->actor->url;
+ if ($related_atom['success']) {
+ logger('GNU Social workaround to fetch XML for URI '.$related_uri, LOGGER_DEBUG);
+ $xml = $related_atom['body'];
+ }
+ }
- $details = self::get_actor_details($actor, $uid, $parent["contact-id"]);
+ // Even more worse workaround for GNU Social ;-)
+ if ($xml == '') {
+ $related_guess = ostatus::convert_href($related_uri);
+ $related_atom = z_fetch_url(str_replace('/notice/', '/api/statuses/show/', $related_guess).'.atom');
- // Do we only want to import threads that were started by our contacts?
- if ($details["not_following"] AND $new_parent AND get_config('system','ostatus_full_threads')) {
- logger("Don't import uri ".$first_id." because user ".$uid." doesn't follow the person ".$actor, LOGGER_DEBUG);
- continue;
+ if ($related_atom['success']) {
+ logger('GNU Social workaround 2 to fetch XML for URI '.$related_uri, LOGGER_DEBUG);
+ $xml = $related_atom['body'];
}
+ }
- /// @TODO One statment is okay (until if () )
- $arr = array();
- $arr["network"] = $details["network"];
- $arr["uri"] = $single_conv->id;
- $arr["plink"] = $plink;
- $arr["uid"] = $uid;
- $arr["contact-id"] = $details["contact_id"];
- $arr["parent-uri"] = $parent_uri;
- $arr["created"] = $single_conv->published;
- $arr["edited"] = $single_conv->published;
- $arr["owner-name"] = $single_conv->actor->displayName;
- if ($arr["owner-name"] == '')
- $arr["owner-name"] = $single_conv->actor->contact->displayName;
- if ($arr["owner-name"] == '')
- $arr["owner-name"] = $single_conv->actor->portablecontacts_net->displayName;
-
- $arr["owner-link"] = $actor;
- $arr["owner-avatar"] = Probe::fixAvatar($single_conv->actor->image->url, $arr["owner-link"]);
-
- $arr["author-name"] = $arr["owner-name"];
- $arr["author-link"] = $arr["owner-link"];
- $arr["author-avatar"] = $arr["owner-avatar"];
- $arr["body"] = add_page_info_to_body(html2bbcode($single_conv->content));
-
- if (isset($single_conv->status_net->conversation)) {
- $arr['conversation-uri'] = $single_conv->status_net->conversation;
+ // Finally we take the data that we fetched from "ostatus:conversation"
+ if ($xml == '') {
+ $condition = array('item-uri' => $related_uri, 'protocol' => PROTOCOL_SPLITTED_CONV);
+ $conversation = dba::select('conversation', array('source'), $condition, array('limit' => 1));
+ if (dbm::is_result($conversation)) {
+ $stored = true;
+ logger('Got cached XML from conversation for URI '.$related_uri, LOGGER_DEBUG);
+ $xml = $conversation['source'];
}
+ }
- if (isset($single_conv->status_net->notice_info->source))
- $arr["app"] = strip_tags($single_conv->status_net->notice_info->source);
- elseif (isset($single_conv->statusnet->notice_info->source))
- $arr["app"] = strip_tags($single_conv->statusnet->notice_info->source);
- elseif (isset($single_conv->statusnet_notice_info->source))
- $arr["app"] = strip_tags($single_conv->statusnet_notice_info->source);
- elseif (isset($single_conv->provider->displayName))
- $arr["app"] = $single_conv->provider->displayName;
- else
- $arr["app"] = "OStatus";
-
-
- $arr["source"] = json_encode($single_conv);
- $arr["protocol"] = PROTOCOL_GS_CONVERSATION;
-
- $arr["verb"] = $parent["verb"];
- $arr["visible"] = $parent["visible"];
- $arr["location"] = $single_conv->location->displayName;
- $arr["coord"] = trim($single_conv->location->lat." ".$single_conv->location->lon);
-
- // Is it a reshared item?
- if (isset($single_conv->verb) AND ($single_conv->verb == "share") AND isset($single_conv->object)) {
- if (is_array($single_conv->object))
- $single_conv->object = $single_conv->object[0];
-
- logger("Found reshared item ".$single_conv->object->id);
-
- // $single_conv->object->context->conversation;
-
- if (isset($single_conv->object->object->id))
- $arr["uri"] = $single_conv->object->object->id;
- else
- $arr["uri"] = $single_conv->object->id;
-
- if (isset($single_conv->object->object->url))
- $plink = self::convert_href($single_conv->object->object->url);
- else
- $plink = self::convert_href($single_conv->object->url);
-
- if (isset($single_conv->object->object->content))
- $arr["body"] = add_page_info_to_body(html2bbcode($single_conv->object->object->content));
- else
- $arr["body"] = add_page_info_to_body(html2bbcode($single_conv->object->content));
-
- $arr["plink"] = $plink;
+ if ($xml != '') {
+ self::process($xml, $importer, $contact, $hub, $stored, false);
+ } else {
+ logger("XML couldn't be fetched for URI: ".$related_uri." - href: ".$related, LOGGER_DEBUG);
+ }
+ return;
+ }
- $arr["created"] = $single_conv->object->published;
- $arr["edited"] = $single_conv->object->published;
+ /**
+ * @brief Processes the XML for a repeated post
+ *
+ * @param object $xpath The xpath object
+ * @param object $entry The xml entry that is processed
+ * @param array $item The item array
+ * @param array $importer user record of the importing user
+ *
+ * @return array with data from links
+ */
+ private static function processRepeatedItem($xpath, $entry, &$item, $importer) {
+ $activityobjects = $xpath->query('activity:object', $entry)->item(0);
- $arr["author-name"] = $single_conv->object->actor->displayName;
- if ($arr["owner-name"] == '') {
- $arr["author-name"] = $single_conv->object->actor->contact->displayName;
- }
- $arr["author-link"] = $single_conv->object->actor->url;
- $arr["author-avatar"] = Probe::fixAvatar($single_conv->object->actor->image->url, $arr["author-link"]);
+ if (!is_object($activityobjects)) {
+ return array();
+ }
- $arr["app"] = $single_conv->object->provider->displayName."#";
- //$arr["verb"] = $single_conv->object->verb;
+ $link_data = array();
- $arr["location"] = $single_conv->object->location->displayName;
- $arr["coord"] = trim($single_conv->object->location->lat." ".$single_conv->object->location->lon);
- }
+ $orig_uri = $xpath->query('atom:id/text()', $activityobjects)->item(0)->nodeValue;
- if ($arr["location"] == "")
- unset($arr["location"]);
+ $links = $xpath->query("atom:link", $activityobjects);
+ if ($links) {
+ $link_data = self::processLinks($links, $item);
+ }
- if ($arr["coord"] == "")
- unset($arr["coord"]);
+ $orig_body = $xpath->query('atom:content/text()', $activityobjects)->item(0)->nodeValue;
+ $orig_created = $xpath->query('atom:published/text()', $activityobjects)->item(0)->nodeValue;
+ $orig_edited = $xpath->query('atom:updated/text()', $activityobjects)->item(0)->nodeValue;
- // Copy fields from given item array
- if (isset($item["uri"]) AND (($item["uri"] == $arr["uri"]) OR ($item["uri"] == $single_conv->id))) {
- $copy_fields = array("owner-name", "owner-link", "owner-avatar", "author-name", "author-link", "author-avatar",
- "gravity", "body", "object-type", "object", "verb", "created", "edited", "coord", "tag",
- "title", "attach", "app", "type", "location", "contact-id", "uri");
- foreach ($copy_fields AS $field)
- if (isset($item[$field]))
- $arr[$field] = $item[$field];
+ $orig_contact = $contact;
+ $orig_author = self::fetchauthor($xpath, $activityobjects, $importer, $orig_contact, false);
- }
+ $item["author-name"] = $orig_author["author-name"];
+ $item["author-link"] = $orig_author["author-link"];
+ $item["author-avatar"] = $orig_author["author-avatar"];
- $newitem = item_store($arr);
- if (!$newitem) {
- logger("Item wasn't stored ".print_r($arr, true), LOGGER_DEBUG);
- continue;
- }
+ $item["body"] = html2bbcode($orig_body);
+ $item["created"] = $orig_created;
+ $item["edited"] = $orig_edited;
- if (isset($item["uri"]) AND ($item["uri"] == $arr["uri"])) {
- $item = array();
- $item_stored = $newitem;
- }
+ $item["uri"] = $orig_uri;
- logger('Stored new item '.$plink.' for parent '.$arr["parent-uri"].' under id '.$newitem, LOGGER_DEBUG);
+ $item["verb"] = $xpath->query('activity:verb/text()', $activityobjects)->item(0)->nodeValue;
- // Add the conversation entry (but don't fetch the whole conversation)
- self::store_conversation($newitem, $conversation_url);
+ $item["object-type"] = $xpath->query('activity:object-type/text()', $activityobjects)->item(0)->nodeValue;
- // If the newly created item is the top item then change the parent settings of the thread
- // This shouldn't happen anymore. This is supposed to be absolote.
- if ($arr["uri"] == $first_id) {
- logger('setting new parent to id '.$newitem);
- $new_parents = q("SELECT `id`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `uid` = %d AND `id` = %d LIMIT 1",
- intval($uid), intval($newitem));
- if ($new_parents)
- $parent = $new_parents[0];
+ $inreplyto = $xpath->query('thr:in-reply-to', $activityobjects);
+ if (is_object($inreplyto->item(0))) {
+ foreach ($inreplyto->item(0)->attributes AS $attributes) {
+ if ($attributes->name == "ref") {
+ $item["parent-uri"] = $attributes->textContent;
+ }
}
}
- if (($item_stored < 0) AND (count($item) > 0)) {
+ return $link_data;
+ }
- if (get_config('system','ostatus_full_threads')) {
- $details = self::get_actor_details($item["owner-link"], $uid, $item["contact-id"]);
- if ($details["not_following"]) {
- logger("Don't import uri ".$item["uri"]." because user ".$uid." doesn't follow the person ".$item["owner-link"], LOGGER_DEBUG);
- return false;
+ /**
+ * @brief Processes links in the XML
+ *
+ * @param object $links The xml data that contain links
+ * @param array $item The item array
+ *
+ * @return array with data from the links
+ */
+ private static function processLinks($links, &$item) {
+ $link_data = array('add_body' => '', 'self' => '');
+
+ foreach ($links AS $link) {
+ $attribute = self::read_attributes($link);
+
+ if (($attribute['rel'] != "") && ($attribute['href'] != "")) {
+ switch ($attribute['rel']) {
+ case "alternate":
+ $item["plink"] = $attribute['href'];
+ if (($item["object-type"] == ACTIVITY_OBJ_QUESTION) ||
+ ($item["object-type"] == ACTIVITY_OBJ_EVENT)) {
+ $item["body"] .= add_page_info($attribute['href']);
+ }
+ break;
+ case "ostatus:conversation":
+ $link_data['conversation'] = $attribute['href'];
+ $item['conversation-href'] = $link_data['conversation'];
+ if (!isset($item['conversation-uri'])) {
+ $item['conversation-uri'] = $item['conversation-href'];
+ }
+ break;
+ case "enclosure":
+ $filetype = strtolower(substr($attribute['type'], 0, strpos($attribute['type'],'/')));
+ if ($filetype == 'image') {
+ $link_data['add_body'] .= "\n[img]".$attribute['href'].'[/img]';
+ } else {
+ if (strlen($item["attach"])) {
+ $item["attach"] .= ',';
+ }
+ if (!isset($attribute['length'])) {
+ $attribute['length'] = "0";
+ }
+ $item["attach"] .= '[attach]href="'.$attribute['href'].'" length="'.$attribute['length'].'" type="'.$attribute['type'].'" title="'.$attribute['title'].'"[/attach]';
+ }
+ break;
+ case "related":
+ if ($item["object-type"] != ACTIVITY_OBJ_BOOKMARK) {
+ if (!isset($item["parent-uri"])) {
+ $item["parent-uri"] = $attribute['href'];
+ }
+ $link_data['related'] = $attribute['href'];
+ } else {
+ $item["body"] .= add_page_info($attribute['href']);
+ }
+ break;
+ case "self":
+ if ($item["plink"] == '') {
+ $item["plink"] = $attribute['href'];
+ }
+ $link_data['self'] = $attribute['href'];
+ break;
}
}
-
- $item_stored = item_store($item, $all_threads);
- if ($item_stored) {
- logger("Uri ".$item["uri"]." wasn't found in conversation ".$conversation_url, LOGGER_DEBUG);
- self::store_conversation($item_stored, $conversation_url);
- }
}
-
- return($item_stored);
+ return $link_data;
}
- /**
- * @brief Stores conversation data into the database
+/**
+ * @brief Create an url out of an uri
*
- * @param integer $itemid The id of the item
- * @param string $conversation_url The uri of the conversation
+ * @param string $href URI in the format "parameter1:parameter1:..."
+ *
+ * @return string URL in the format http(s)://....
*/
- private function store_conversation($itemid, $conversation_url) {
+ public static function convert_href($href) {
+ $elements = explode(":",$href);
- $conversation_url = self::convert_href($conversation_url);
+ if ((count($elements) <= 2) || ($elements[0] != "tag"))
+ return $href;
- $messages = q("SELECT `uid`, `parent`, `created`, `received`, `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($itemid));
- if (!$messages)
- return;
- $message = $messages[0];
+ $server = explode(",", $elements[1]);
+ $conversation = explode("=", $elements[2]);
- // Store conversation url if not done before
- $conversation = q("SELECT `url` FROM `term` WHERE `uid` = %d AND `oid` = %d AND `otype` = %d AND `type` = %d",
- intval($message["uid"]), intval($itemid), intval(TERM_OBJ_POST), intval(TERM_CONVERSATION));
+ if ((count($elements) == 4) && ($elements[2] == "post"))
+ return "http://".$server[0]."/notice/".$elements[3];
- if (!$conversation) {
- $r = q("INSERT INTO `term` (`uid`, `oid`, `otype`, `type`, `term`, `url`, `created`, `received`, `guid`) VALUES (%d, %d, %d, %d, '%s', '%s', '%s', '%s', '%s')",
- intval($message["uid"]), intval($itemid), intval(TERM_OBJ_POST), intval(TERM_CONVERSATION),
- dbesc($message["created"]), dbesc($conversation_url), dbesc($message["created"]), dbesc($message["received"]), dbesc($message["guid"]));
- logger('Storing conversation url '.$conversation_url.' for id '.$itemid);
+ if ((count($conversation) != 2) || ($conversation[1] =="")) {
+ return $href;
+ }
+ if ($elements[3] == "objectType=thread") {
+ return "http://".$server[0]."/conversation/".$conversation[1];
+ } else {
+ return "http://".$server[0]."/notice/".$conversation[1];
}
+ return $href;
}
/**
*
* @return string The guid if the post is a reshare
*/
- private function get_reshared_guid($item) {
+ private static function get_reshared_guid($item) {
$body = trim($item["body"]);
// Skip if it isn't a pure repeated messages
// Does it start with a share?
if (strpos($body, "[share") > 0)
- return("");
+ return "";
// Does it end with a share?
if (strlen($body) > (strrpos($body, "[/share]") + 8))
- return("");
+ return "";
$attributes = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism","$1",$body);
// Skip if there is no shared message in there
if ($body == $attributes)
- return(false);
+ return false;
$guid = "";
preg_match("/guid='(.*?)'/ism", $attributes, $matches);
*
* @return string The cleaned body
*/
- private function format_picture_post($body) {
+ private static function format_picture_post($body) {
$siteinfo = get_attached_data($body);
if (($siteinfo["type"] == "photo")) {
*
* @return object header root element
*/
- private function add_header($doc, $owner) {
+ private static function add_header($doc, $owner) {
$a = get_app();
$attributes = array("uri" => "https://friendi.ca", "version" => FRIENDICA_VERSION."-".DB_UPDATE_VERSION);
xml::add_element($doc, $root, "generator", FRIENDICA_PLATFORM, $attributes);
- xml::add_element($doc, $root, "id", App::get_baseurl()."/profile/".$owner["nick"]);
+ xml::add_element($doc, $root, "id", System::baseUrl()."/profile/".$owner["nick"]);
xml::add_element($doc, $root, "title", sprintf("%s timeline", $owner["name"]));
xml::add_element($doc, $root, "subtitle", sprintf("Updates from %s on %s", $owner["name"], $a->config["sitename"]));
xml::add_element($doc, $root, "logo", $owner["photo"]);
xml::add_element($doc, $root, "link", "", $attributes);
/// @TODO We have to find out what this is
- /// $attributes = array("href" => App::get_baseurl()."/sup",
+ /// $attributes = array("href" => System::baseUrl()."/sup",
/// "rel" => "http://api.friendfeed.com/2008/03#sup",
/// "type" => "application/json");
/// xml::add_element($doc, $root, "link", "", $attributes);
- self::hublinks($doc, $root);
+ self::hublinks($doc, $root, $owner["nick"]);
- $attributes = array("href" => App::get_baseurl()."/salmon/".$owner["nick"], "rel" => "salmon");
+ $attributes = array("href" => System::baseUrl()."/salmon/".$owner["nick"], "rel" => "salmon");
xml::add_element($doc, $root, "link", "", $attributes);
- $attributes = array("href" => App::get_baseurl()."/salmon/".$owner["nick"], "rel" => "http://salmon-protocol.org/ns/salmon-replies");
+ $attributes = array("href" => System::baseUrl()."/salmon/".$owner["nick"], "rel" => "http://salmon-protocol.org/ns/salmon-replies");
xml::add_element($doc, $root, "link", "", $attributes);
- $attributes = array("href" => App::get_baseurl()."/salmon/".$owner["nick"], "rel" => "http://salmon-protocol.org/ns/salmon-mention");
+ $attributes = array("href" => System::baseUrl()."/salmon/".$owner["nick"], "rel" => "http://salmon-protocol.org/ns/salmon-mention");
xml::add_element($doc, $root, "link", "", $attributes);
- $attributes = array("href" => App::get_baseurl()."/api/statuses/user_timeline/".$owner["nick"].".atom",
+ $attributes = array("href" => System::baseUrl()."/api/statuses/user_timeline/".$owner["nick"].".atom",
"rel" => "self", "type" => "application/atom+xml");
xml::add_element($doc, $root, "link", "", $attributes);
* @param object $doc XML document
* @param object $root XML root element where the hub links are added
*/
- public static function hublinks($doc, $root) {
- $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;
- if ($h === '[internal]')
- $h = App::get_baseurl() . '/pubsubhubbub';
- xml::add_element($doc, $root, "link", "", array("href" => $h, "rel" => "hub"));
- }
- }
- }
+ public static function hublinks($doc, $root, $nick) {
+ $h = System::baseUrl() . '/pubsubhubbub/'.$nick;
+ xml::add_element($doc, $root, "link", "", array("href" => $h, "rel" => "hub"));
}
/**
* @param object $root XML root element where the hub links are added
* @param array $item Data of the item that is to be posted
*/
- private function get_attachment($doc, $root, $item) {
+ private static function get_attachment($doc, $root, $item) {
$o = "";
$siteinfo = get_attached_data($item["body"]);
break;
}
- if (($siteinfo["type"] != "photo") AND isset($siteinfo["image"])) {
+ if (!Config::get('system', 'ostatus_not_attach_preview') && ($siteinfo["type"] != "photo") && isset($siteinfo["image"])) {
$imgdata = get_photo_info($siteinfo["image"]);
$attributes = array("rel" => "enclosure",
"href" => $siteinfo["image"],
*
* @return object author element
*/
- private function add_author($doc, $owner) {
+ private static function add_author($doc, $owner) {
$r = q("SELECT `homepage`, `publish` FROM `profile` WHERE `uid` = %d AND `is-default` LIMIT 1", intval($owner["uid"]));
- if ($r)
+ if (dbm::is_result($r)) {
$profile = $r[0];
-
+ }
$author = $doc->createElement("author");
xml::add_element($doc, $author, "id", $owner["url"]);
xml::add_element($doc, $author, "activity:object-type", ACTIVITY_OBJ_PERSON);
}
if (count($profile)) {
- xml::add_element($doc, $author, "followers", "", array("url" => App::get_baseurl()."/viewcontacts/".$owner["nick"]));
+ xml::add_element($doc, $author, "followers", "", array("url" => System::baseUrl()."/viewcontacts/".$owner["nick"]));
xml::add_element($doc, $author, "statusnet:profile_info", "", array("local_id" => $owner["uid"]));
}
*
* @return string activity
*/
- function construct_verb($item) {
+ private static function construct_verb($item) {
if ($item['verb'])
return $item['verb'];
return ACTIVITY_POST;
*
* @return string Object type
*/
- function construct_objecttype($item) {
+ private static function construct_objecttype($item) {
if (in_array($item['object-type'], array(ACTIVITY_OBJ_NOTE, ACTIVITY_OBJ_COMMENT)))
return $item['object-type'];
return ACTIVITY_OBJ_NOTE;
*
* @return object Entry element
*/
- private function entry($doc, $item, $owner, $toplevel = false) {
+ private static function entry($doc, $item, $owner, $toplevel = false) {
$repeated_guid = self::get_reshared_guid($item);
if ($repeated_guid != "")
$xml = self::reshare_entry($doc, $item, $owner, $repeated_guid, $toplevel);
*
* @return object Source element
*/
- private function source_entry($doc, $contact) {
+ private static function source_entry($doc, $contact) {
$source = $doc->createElement("source");
xml::add_element($doc, $source, "id", $contact["poll"]);
xml::add_element($doc, $source, "title", $contact["name"]);
*
* @return array Contact array
*/
- private function contact_entry($url, $owner) {
+ private static function contact_entry($url, $owner) {
$r = q("SELECT * FROM `contact` WHERE `nurl` = '%s' AND `uid` IN (0, %d) ORDER BY `uid` DESC LIMIT 1",
dbesc(normalise_link($url)), intval($owner["uid"]));
- if ($r) {
+ if (dbm::is_result($r)) {
$contact = $r[0];
$contact["uid"] = -1;
}
- if (!$r) {
+ if (!dbm::is_result($r)) {
$r = q("SELECT * FROM `gcontact` WHERE `nurl` = '%s' LIMIT 1",
dbesc(normalise_link($url)));
- if ($r) {
+ if (dbm::is_result($r)) {
$contact = $r[0];
$contact["uid"] = -1;
$contact["success_update"] = $contact["updated"];
}
}
- if (!$r)
+ if (!dbm::is_result($r))
$contact = owner;
if (!isset($contact["poll"])) {
*
* @return object Entry element
*/
- private function reshare_entry($doc, $item, $owner, $repeated_guid, $toplevel) {
+ private static function reshare_entry($doc, $item, $owner, $repeated_guid, $toplevel) {
- if (($item["id"] != $item["parent"]) AND (normalise_link($item["author-link"]) != normalise_link($owner["url"]))) {
+ if (($item["id"] != $item["parent"]) && (normalise_link($item["author-link"]) != normalise_link($owner["url"]))) {
logger("OStatus entry is from author ".$owner["url"]." - not from ".$item["author-link"].". Quitting.", LOGGER_DEBUG);
}
$r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' AND NOT `private` AND `network` IN ('%s', '%s', '%s') LIMIT 1",
intval($owner["uid"]), dbesc($repeated_guid),
dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS));
- if ($r)
+ if (dbm::is_result($r)) {
$repeated_item = $r[0];
- else
+ } else {
return false;
-
+ }
$contact = self::contact_entry($repeated_item['author-link'], $owner);
$parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
*
* @return object Entry element with "like"
*/
- private function like_entry($doc, $item, $owner, $toplevel) {
+ private static function like_entry($doc, $item, $owner, $toplevel) {
- if (($item["id"] != $item["parent"]) AND (normalise_link($item["author-link"]) != normalise_link($owner["url"]))) {
+ if (($item["id"] != $item["parent"]) && (normalise_link($item["author-link"]) != normalise_link($owner["url"]))) {
logger("OStatus entry is from author ".$owner["url"]." - not from ".$item["author-link"].". Quitting.", LOGGER_DEBUG);
}
*
* @return object author element
*/
- private function add_person_object($doc, $owner, $contact) {
+ private static function add_person_object($doc, $owner, $contact) {
$object = $doc->createElement("activity:object");
xml::add_element($doc, $object, "activity:object-type", ACTIVITY_OBJ_PERSON);
*
* @return object Entry element
*/
- private function follow_entry($doc, $item, $owner, $toplevel) {
+ private static function follow_entry($doc, $item, $owner, $toplevel) {
$item["id"] = $item["parent"] = 0;
$item["created"] = $item["edited"] = date("c");
*
* @return object Entry element
*/
- private function note_entry($doc, $item, $owner, $toplevel) {
+ private static function note_entry($doc, $item, $owner, $toplevel) {
- if (($item["id"] != $item["parent"]) AND (normalise_link($item["author-link"]) != normalise_link($owner["url"]))) {
+ if (($item["id"] != $item["parent"]) && (normalise_link($item["author-link"]) != normalise_link($owner["url"]))) {
logger("OStatus entry is from author ".$owner["url"]." - not from ".$item["author-link"].". Quitting.", LOGGER_DEBUG);
}
*
* @return string The title for the element
*/
- private function entry_header($doc, &$entry, $owner, $toplevel) {
+ private static function entry_header($doc, &$entry, $owner, $toplevel) {
/// @todo Check if this title stuff is really needed (I guess not)
if (!$toplevel) {
$entry = $doc->createElement("entry");
* @param string $verb The activity verb
* @param bool $complete Add the "status_net" element?
*/
- private function entry_content($doc, $entry, $item, $owner, $title, $verb = "", $complete = true) {
+ private static function entry_content($doc, $entry, $item, $owner, $title, $verb = "", $complete = true) {
if ($verb == "")
$verb = self::construct_verb($item);
xml::add_element($doc, $entry, "content", $body, array("type" => "html"));
xml::add_element($doc, $entry, "link", "", array("rel" => "alternate", "type" => "text/html",
- "href" => App::get_baseurl()."/display/".$item["guid"]));
+ "href" => System::baseUrl()."/display/".$item["guid"]));
- if ($complete AND ($item["id"] > 0))
+ if ($complete && ($item["id"] > 0))
xml::add_element($doc, $entry, "status_net", "", array("notice_id" => $item["id"]));
xml::add_element($doc, $entry, "activity:verb", $verb);
* @param array $owner Contact data of the poster
* @param $complete
*/
- private function entry_footer($doc, $entry, $item, $owner, $complete = true) {
+ private static function entry_footer($doc, $entry, $item, $owner, $complete = true) {
$mentioned = array();
- if (($item['parent'] != $item['id']) OR ($item['parent-uri'] !== $item['uri']) OR (($item['thr-parent'] !== '') AND ($item['thr-parent'] !== $item['uri']))) {
+ if (($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
$parent = q("SELECT `guid`, `author-link`, `owner-link` FROM `item` WHERE `id` = %d", intval($item["parent"]));
$parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
} else {
$mentioned[$parent[0]["author-link"]] = $parent[0]["author-link"];
$mentioned[$parent[0]["owner-link"]] = $parent[0]["owner-link"];
- $parent_plink = App::get_baseurl()."/display/".$parent[0]["guid"];
+ $parent_plink = System::baseUrl()."/display/".$parent[0]["guid"];
}
$attributes = array(
}
if (intval($item["parent"]) > 0) {
- $conversation_href = App::get_baseurl()."/display/".$owner["nick"]."/".$item["parent"];
+ $conversation_href = System::baseUrl()."/display/".$owner["nick"]."/".$item["parent"];
$conversation_uri = $conversation_href;
if (isset($parent_item)) {
$tags = item_getfeedtags($item);
- if(count($tags))
- foreach($tags as $t)
+ if (count($tags))
+ foreach ($tags as $t)
if ($t[0] == "@")
$mentioned[$t[1]] = $t[1];
$r = q("SELECT `forum`, `prv` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s'",
intval($owner["uid"]),
dbesc(normalise_link($mention)));
- if ($r[0]["forum"] OR $r[0]["prv"])
+ if ($r[0]["forum"] || $r[0]["prv"])
xml::add_element($doc, $entry, "link", "", array("rel" => "mentioned",
"ostatus:object-type" => ACTIVITY_OBJ_GROUP,
"href" => $mention));
xml::add_element($doc, $entry, "mastodon:scope", "public");
}
- if(count($tags))
- foreach($tags as $t)
+ if (count($tags))
+ foreach ($tags as $t)
if ($t[0] != "@")
xml::add_element($doc, $entry, "category", "", array("term" => $t[2]));
self::get_attachment($doc, $entry, $item);
- if ($complete AND ($item["id"] > 0)) {
+ if ($complete && ($item["id"] > 0)) {
$app = $item["app"];
if ($app == "")
$app = "web";
* @param App $a The application class
* @param string $owner_nick Nickname of the feed owner
* @param string $last_update Date of the last update
+ * @param integer $max_items Number of maximum items to fetch
*
* @return string XML feed
*/
- public static function feed(App $a, $owner_nick, $last_update) {
+ public static function feed(App $a, $owner_nick, &$last_update, $max_items = 300) {
+ $stamp = microtime(true);
+
+ $cachekey = "ostatus:feed:".$owner_nick.":".$last_update;
+
+ $previous_created = $last_update;
+
+ $result = Cache::get($cachekey);
+ if (!is_null($result)) {
+ logger('Feed duration: '.number_format(microtime(true) - $stamp, 3).' - '.$owner_nick.' - '.$previous_created.' (cached)', LOGGER_DEBUG);
+ $last_update = $result['last_update'];
+ return $result['feed'];
+ }
$r = q("SELECT `contact`.*, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
WHERE `contact`.`self` AND `user`.`nickname` = '%s' LIMIT 1",
dbesc($owner_nick));
- if (!$r)
+ if (!dbm::is_result($r)) {
return;
+ }
$owner = $r[0];
- if (!strlen($last_update))
+ if (!strlen($last_update)) {
$last_update = 'now -30 days';
+ }
$check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
$authorid = get_contact($owner["url"], 0);
`item`.`author-id` = %d AND `item`.`created` > '%s' AND
NOT `item`.`deleted` AND NOT `item`.`private` AND
`thread`.`network` IN ('%s', '%s')
- ORDER BY `item`.`created` DESC LIMIT 300",
+ ORDER BY `item`.`created` DESC LIMIT %d",
intval($owner["uid"]), intval($owner["id"]),
intval($authorid), dbesc($check_date),
- dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN));
-
-/* 2016-10-23: The old query will be kept until we are sure that the query above is a good and fast replacement
+ dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN), intval($max_items));
- $items = q("SELECT `item`.*, `item`.`id` AS `item_id` FROM `item`
- STRAIGHT_JOIN `thread` ON `thread`.`iid` = `item`.`parent`
- LEFT JOIN `item` AS `thritem` ON `thritem`.`uri`=`item`.`thr-parent` AND `thritem`.`uid`=`item`.`uid`
- WHERE `item`.`uid` = %d AND `item`.`received` > '%s' AND NOT `item`.`private` AND NOT `item`.`deleted`
- AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
- AND ((`item`.`wall` AND (`item`.`parent` = `item`.`id`))
- OR (`item`.`network` = '%s' AND ((`thread`.`network` IN ('%s', '%s')) OR (`thritem`.`network` IN ('%s', '%s')))) AND `thread`.`mention`)
- AND ((`item`.`owner-link` IN ('%s', '%s') AND (`item`.`parent` = `item`.`id`))
- OR (`item`.`author-link` IN ('%s', '%s')))
- ORDER BY `item`.`id` DESC
- LIMIT 0, 300",
- intval($owner["uid"]), dbesc($check_date), dbesc(NETWORK_DFRN),
- //dbesc(NETWORK_OSTATUS), dbesc(NETWORK_OSTATUS),
- //dbesc(NETWORK_OSTATUS), dbesc(NETWORK_OSTATUS),
- dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN),
- dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN),
- dbesc($owner["nurl"]), dbesc(str_replace("http://", "https://", $owner["nurl"])),
- dbesc($owner["nurl"]), dbesc(str_replace("http://", "https://", $owner["nurl"]))
- );
-*/
$doc = new DOMDocument('1.0', 'utf-8');
$doc->formatOutput = true;
}
$entry = self::entry($doc, $item, $owner);
$root->appendChild($entry);
+
+ if ($last_update < $item['created']) {
+ $last_update = $item['created'];
+ }
}
- return(trim($doc->saveXML()));
+ $feeddata = trim($doc->saveXML());
+
+ $msg = array('feed' => $feeddata, 'last_update' => $last_update);
+ Cache::set($cachekey, $msg, CACHE_QUARTER_HOUR);
+
+ logger('Feed duration: '.number_format(microtime(true) - $stamp, 3).' - '.$owner_nick.' - '.$previous_created, LOGGER_DEBUG);
+
+ return $feeddata;
}
/**
$doc->appendChild($entry);
- return(trim($doc->saveXML()));
+ return trim($doc->saveXML());
}
}