X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=include%2Fostatus.php;h=5df956b2c9c93e63a69a0cef83bf7f1279ccdd9a;hb=8a0bec00c4ef9ebf8c122b64190e231bb7a43294;hp=e3724f3bff65035023f71f5093e133916412a1e6;hpb=e2b37fd7e4c3d2b6ee487ff1e086d5878ac98b88;p=friendica.git diff --git a/include/ostatus.php b/include/ostatus.php index e3724f3bff..5df956b2c9 100644 --- a/include/ostatus.php +++ b/include/ostatus.php @@ -4,8 +4,14 @@ */ use Friendica\App; +use Friendica\Core\Cache; use Friendica\Core\Config; +use Friendica\Core\System; +use Friendica\Database\DBM; +use Friendica\Model\GlobalContact; use Friendica\Network\Probe; +use Friendica\Util\Lock; +use Friendica\Util\XML; require_once 'include/Contact.php'; require_once 'include/threads.php'; @@ -14,37 +20,32 @@ require_once 'include/bbcode.php'; require_once 'include/items.php'; require_once 'mod/share.php'; require_once 'include/enotify.php'; -require_once 'include/socgraph.php'; require_once 'include/Photo.php'; -require_once 'include/probe.php'; require_once 'include/follow.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 +class ostatus +{ + 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 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 + * @param object $xpath The xpath object + * @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 static 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; $author["author-name"] = $xpath->evaluate('atom:author/atom:name/text()', $context)->item(0)->nodeValue; @@ -54,46 +55,71 @@ class ostatus { $alternate = $xpath->query("atom:author/atom:link[@rel='alternate']", $context)->item(0)->attributes; if (is_object($alternate)) { - foreach ($alternate AS $attributes) { + foreach ($alternate as $attributes) { 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"]; + if (DBM::is_result($r)) { + $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"]; } } $avatarlist = array(); $avatars = $xpath->query("atom:author/atom:link[@rel='avatar']", $context); - foreach ($avatars AS $avatar) { + foreach ($avatars as $avatar) { $href = ""; $width = 0; - foreach ($avatar->attributes AS $attributes) { + foreach ($avatar->attributes as $attributes) { if ($attributes->name == "href") { $href = $attributes->textContent; } @@ -120,10 +146,15 @@ class ostatus { $author["owner-avatar"] = $author["author-avatar"]; // Only update the contacts if it is an OStatus contact - if ($r && !$onlyfetch && ($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 != "") @@ -134,55 +165,58 @@ class ostatus { // 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 != "") + if ($value != "") { $contact["alias"] = $value; + } $value = $xpath->evaluate('atom:author/poco:displayName/text()', $context)->item(0)->nodeValue; - if ($value != "") + if ($value != "") { $contact["name"] = $value; + } $value = $xpath->evaluate('atom:author/poco:preferredUsername/text()', $context)->item(0)->nodeValue; - if ($value != "") + if ($value != "") { $contact["nick"] = $value; + } $value = $xpath->evaluate('atom:author/poco:note/text()', $context)->item(0)->nodeValue; - if ($value != "") + if ($value != "") { $contact["about"] = html2bbcode($value); + } $value = $xpath->evaluate('atom:author/poco:address/poco:formatted/text()', $context)->item(0)->nodeValue; - if ($value != "") + if ($value != "") { $contact["location"] = $value; + } - if (($contact["name"] != $r[0]["name"]) || ($contact["nick"] != $r[0]["nick"]) || ($contact["about"] != $r[0]["about"]) || - ($contact["alias"] != $r[0]["alias"]) || ($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"]) && ($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); @@ -191,9 +225,9 @@ class ostatus { $contact["generation"] = 2; $contact["hide"] = false; // OStatus contacts are never hidden $contact["photo"] = $author["author-avatar"]; - $gcid = update_gcontact($contact); + $gcid = GlobalContact::update($contact); - link_gcontact($gcid, $contact["uid"], $contact["id"]); + GlobalContact::link($gcid, $contact["uid"], $contact["id"]); } return $author; @@ -202,15 +236,16 @@ class ostatus { /** * @brief Fetches author data from a given XML string * - * @param string $xml The XML - * @param array $importer user record of the importing user + * @param string $xml The XML + * @param array $importer user record of the importing user * * @return array Array of author related entries for the item */ - public static function salmon_author($xml, $importer) { - - if ($xml == "") + public static function salmon_author($xml, $importer) + { + if ($xml == "") { return; + } $doc = new DOMDocument(); @$doc->loadXML($xml); @@ -227,7 +262,7 @@ class ostatus { $entries = $xpath->query('/atom:entry'); - foreach ($entries AS $entry) { + foreach ($entries as $entry) { // fetch the author $author = self::fetchauthor($xpath, $entry, $importer, $contact, true); return $author; @@ -241,10 +276,11 @@ class ostatus { * * @return array attributes */ - private static function read_attributes($element) { + private static function read_attributes($element) + { $attribute = array(); - foreach ($element->attributes AS $attributes) { + foreach ($element->attributes as $attributes) { $attribute[$attributes->name] = $attributes->textContent; } @@ -254,22 +290,40 @@ class ostatus { /** * @brief Imports an XML string containing OStatus elements * - * @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 string $xml The XML + * @param array $importer user record of the importing user + * @param array $contact contact + * @param string $hub Called by reference, returns the fetched hub data + */ + 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? */ - public static function import($xml,$importer,&$contact, &$hub) { - /// @todo this function is too long. It has to be split in many parts + 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); @@ -283,16 +337,17 @@ class ostatus { $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) { + foreach ($hub_attributes as $hub_attribute) { if ($hub_attribute->name == "href") { $hub = $hub_attribute->textContent; logger("Found hub ".$hub, LOGGER_DEBUG); } } } + $header = array(); $header["uid"] = $importer["uid"]; $header["network"] = NETWORK_OSTATUS; @@ -301,85 +356,95 @@ class 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) { + foreach ($entries as $entry) { $entrylist[] = $entry; } - foreach (array_reverse($entrylist) AS $entry) { - - $mention = false; + foreach (array_reverse($entrylist) as $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); + $authorelement = $xpath->query('/atom:entry/atom:author', $entry); + + if ($authorelement->length == 0) { + $authorelement = $xpath->query('atom:author', $entry); } - $value = $xpath->evaluate('atom:author/poco:preferredUsername/text()', $context)->item(0)->nodeValue; + + if ($authorelement->length > 0) { + $author = self::fetchauthor($xpath, $entry, $importer, $contact, $stored); + } + + $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) && $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) || ($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; } @@ -402,958 +467,678 @@ class ostatus { $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 ($attributes->name == "href") { - $related = $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'); } - } - } - - $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'] != "") && ($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": - $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 && ($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 != "") || ($item["verb"] == ACTIVITY_SHARE)) { - $activityobjects = $xpath->query('activity:object', $entry)->item(0); - - if (is_object($activityobjects)) { - - $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 && ($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; + private static function deleteNotice($item) + { + $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_contact = $contact; - $orig_author = self::fetchauthor($xpath, $activityobjects, $importer, $orig_contact, false); + // 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"]) + ); - $item["author-name"] = $orig_author["author-name"]; - $item["author-link"] = $orig_author["author-link"]; - $item["author-avatar"] = $orig_author["author-avatar"]; + delete_thread($deleted["id"], $deleted["parent-uri"]); - $item["body"] = add_page_info_to_body(html2bbcode($orig_body)); - $item["created"] = $orig_created; - $item["edited"] = $orig_edited; + logger('Deleted item with uri '.$item['uri'].' for user '.$item['uid']); + } - $item["uri"] = $orig_uri; + /** + * @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["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; + } + } + } - if (!isset($item["plink"])) { - $item["plink"] = $orig_link; - } + $related = ""; - $item["verb"] = $xpath->query('activity:verb/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; + } + } + } - $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; - } + $georsspoint = $xpath->query('georss:point', $entry); + if (!empty($georsspoint) && ($georsspoint->length > 0)) { + $item["coord"] = $georsspoint->item(0)->nodeValue; + } - $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]'; - } + $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)) && - !dbm::is_result($r) && ($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) { - // Store the conversation data. This is normally done in "item_store" - // but since something went wrong, we want to be sure to save the data. - store_conversation($item); - 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) || ($elements[0] != "tag")) - return $href; - - $server = explode(",", $elements[1]); - $conversation = explode("=", $elements[2]); - if ((count($elements) == 4) && ($elements[2] == "post")) - return "http://".$server[0]."/notice/".$elements[3]; + $item["body"] .= $add_body; - if ((count($conversation) != 2) || ($conversation[1] =="")) { - 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"]); } - if ($elements[3] == "objectType=thread") { - return "http://".$server[0]."/conversation/".$conversation[1]; - } else { - return "http://".$server[0]."/notice/".$conversation[1]; - } - return $href; - } - /** - * @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) && !$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 && !$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 static 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; - - 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; + private static function fetchConversation($conversation, $conversation_uri) + { + // 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']; + } } } + } + + if ($xml == '') { + return; + } - $contact["hide"] = false; // OStatus contacts are never hidden - update_gcontact($contact); + self::storeConversation($xml, $conversation, $conversation_uri); } /** - * @brief Fetches the conversation url for a given item link or conversation id + * @brief Store a feed in several conversation entries * - * @param string $self The link to the posting - * @param string $conversation_id The conversation id - * - * @return string The conversation url + * @param string $xml The feed + * @param string $conversation conversation + * @param string $conversation_uri conversation uri */ - private static function fetch_conversation($self, $conversation_id = "") { - - if ($conversation_id != "") { - $elements = explode(":", $conversation_id); - - if ((count($elements) <= 2) || ($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 a shared object from a given conversation object - * - * Sometimes GNU Social seems to fail when returning shared objects. - * Then they don't contains all needed data. - * We then try to find this object in the conversation - * - * @param string $id Message id - * @param object $conversation Conversation object - * - * @return object The shared object - */ - private static function shared_object($id, $conversation) { - if (!is_array($conversation->items)) { - return false; - } - foreach ($conversation->items AS $single_conv) { - if ($single_conv->id == $id) { - return $single_conv; + $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; + } + } } - } - return false; - } - - /** - * @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 static function get_actor_details($actor, $uid, $contact_id) { - $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 static 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) && (count($item) > 0)) || - ($item["verb"] == ACTIVITY_LIKE) || ($conversation_url == "")) { - $item_stored = item_store($item, $all_threads); - return $item_stored; - } - - // 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)); - 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 (!dbm::is_result($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"] && (substr($conv, 0, 7) == "http://")) { - $conv = str_replace("http://", "https://", $conv); - $conv_as = fetch_url($conv."?page=".$pageno); - } elseif (!$conv_arr["success"] && (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'); - - 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; - } + 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; } - $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); + $self_data = z_fetch_url($self); - 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"]) && ($parent["uri"] != "")) { + if (!$self_data['success']) { + return; + } - $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"]) && ($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"] && $new_parent && 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; - } - - 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"; + // 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']; } + } - $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) && ($single_conv->verb == "share") && isset($single_conv->object)) { - if (is_array($single_conv->object)) - $single_conv->object = $single_conv->object[0]; - - // Sometimes GNU Social doesn't returns a complete object - if (!isset($single_conv->object->actor->url)) { - $object = self::shared_object($single_conv->object->id, $conversation); - if (is_object($object)) { - $single_conv->object = $object; - } - } - - if (isset($single_conv->object->actor->url)) { - logger("Found reshared item ".$single_conv->object->id); + 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; + } - // $single_conv->object->context->conversation; + /** + * @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); - 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 (!is_object($activityobjects)) { + return array(); + } - $arr["created"] = $single_conv->object->published; - $arr["edited"] = $single_conv->object->published; + $link_data = array(); - $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"]); + $orig_uri = $xpath->query('atom:id/text()', $activityobjects)->item(0)->nodeValue; - $arr["app"] = $single_conv->object->provider->displayName."#"; - //$arr["verb"] = $single_conv->object->verb; + $links = $xpath->query("atom:link", $activityobjects); + if ($links) { + $link_data = self::processLinks($links, $item); + } - $arr["location"] = $single_conv->object->location->displayName; - $arr["coord"] = trim($single_conv->object->location->lat." ".$single_conv->object->location->lon); - } - } + $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; - if ($arr["location"] == "") - unset($arr["location"]); + $orig_contact = $contact; + $orig_author = self::fetchauthor($xpath, $activityobjects, $importer, $orig_contact, false); - if ($arr["coord"] == "") - unset($arr["coord"]); + $item["author-name"] = $orig_author["author-name"]; + $item["author-link"] = $orig_author["author-link"]; + $item["author-avatar"] = $orig_author["author-avatar"]; - // Copy fields from given item array - if (isset($item["uri"]) && (($item["uri"] == $arr["uri"]) || ($item["uri"] == $single_conv->id))) { - logger('Use stored item array for item with URI '.$item["uri"], LOGGER_DEBUG); - $newitem = item_store($item); - $item = array(); - $item_stored = $newitem; - } else { - $newitem = item_store($arr); - } + $item["body"] = html2bbcode($orig_body); + $item["created"] = $orig_created; + $item["edited"] = $orig_edited; - if (!$newitem) { - logger("Item wasn't stored ".print_r($arr, true), LOGGER_DEBUG); - continue; - } + $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) && (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 static 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; } /** @@ -1363,31 +1148,37 @@ class ostatus { * * @return string The guid if the post is a reshare */ - private static 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) + if (strpos($body, "[share") > 0) { return ""; + } // Does it end with a share? - if (strlen($body) > (strrpos($body, "[/share]") + 8)) + if (strlen($body) > (strrpos($body, "[/share]") + 8)) { return ""; + } - $attributes = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism","$1",$body); + $attributes = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism", "$1", $body); // Skip if there is no shared message in there - if ($body == $attributes) + if ($body == $attributes) { return false; + } $guid = ""; preg_match("/guid='(.*?)'/ism", $attributes, $matches); - if ($matches[1] != "") + if ($matches[1] != "") { $guid = $matches[1]; + } preg_match('/guid="(.*?)"/ism', $attributes, $matches); - if ($matches[1] != "") + if ($matches[1] != "") { $guid = $matches[1]; + } return $guid; } @@ -1399,14 +1190,16 @@ class ostatus { * * @return string The cleaned body */ - private static function format_picture_post($body) { + private static function format_picture_post($body) + { $siteinfo = get_attached_data($body); if (($siteinfo["type"] == "photo")) { - if (isset($siteinfo["preview"])) + if (isset($siteinfo["preview"])) { $preview = $siteinfo["preview"]; - else + } else { $preview = $siteinfo["image"]; + } // Is it a remote picture? Then make a smaller preview here $preview = proxy_url($preview, false, PROXY_SIZE_SMALL); @@ -1415,10 +1208,11 @@ class ostatus { $preview = str_replace(array("-0.jpg", "-0.png"), array("-2.jpg", "-2.png"), $preview); $preview = str_replace(array("-1.jpg", "-1.png"), array("-2.jpg", "-2.png"), $preview); - if (isset($siteinfo["url"])) + if (isset($siteinfo["url"])) { $url = $siteinfo["url"]; - else + } else { $url = $siteinfo["image"]; + } $body = trim($siteinfo["text"])." [url]".$url."[/url]\n[img]".$preview."[/img]"; } @@ -1429,13 +1223,13 @@ class ostatus { /** * @brief Adds the header elements to the XML document * - * @param object $doc XML document - * @param array $owner Contact data of the poster + * @param object $doc XML document + * @param array $owner Contact data of the poster * * @return object header root element */ - private static function add_header($doc, $owner) { - + private static function add_header($doc, $owner) + { $a = get_app(); $root = $doc->createElementNS(NAMESPACE_ATOM1, 'feed'); @@ -1451,39 +1245,39 @@ class ostatus { $root->setAttribute("xmlns:mastodon", NAMESPACE_MASTODON); $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, "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, "updated", datetime_convert("UTC", "UTC", "now", ATOM_TIME)); + XML::add_element($doc, $root, "generator", FRIENDICA_PLATFORM, $attributes); + 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, "updated", datetime_convert("UTC", "UTC", "now", ATOM_TIME)); $author = self::add_author($doc, $owner); $root->appendChild($author); $attributes = array("href" => $owner["url"], "rel" => "alternate", "type" => "text/html"); - xml::add_element($doc, $root, "link", "", $attributes); + 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); + /// XML::add_element($doc, $root, "link", "", $attributes); self::hublinks($doc, $root, $owner["nick"]); - $attributes = array("href" => App::get_baseurl()."/salmon/".$owner["nick"], "rel" => "salmon"); - xml::add_element($doc, $root, "link", "", $attributes); + $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"); - xml::add_element($doc, $root, "link", "", $attributes); + $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"); - xml::add_element($doc, $root, "link", "", $attributes); + $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); + XML::add_element($doc, $root, "link", "", $attributes); return $root; } @@ -1491,22 +1285,25 @@ class ostatus { /** * @brief Add the link to the push hubs to the XML document * - * @param object $doc XML document + * @param object $doc XML document * @param object $root XML root element where the hub links are added + * @param object $nick nick */ - public static function hublinks($doc, $root, $nick) { - $h = App::get_baseurl() . '/pubsubhubbub/'.$nick; - 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")); } /** * @brief Adds attachement data to the XML document * - * @param object $doc XML document + * @param object $doc XML document * @param object $root XML root element where the hub links are added - * @param array $item Data of the item that is to be posted + * @param array $item Data of the item that is to be posted */ - private static function get_attachment($doc, $root, $item) { + private static function get_attachment($doc, $root, $item) + { $o = ""; $siteinfo = get_attached_data($item["body"]); @@ -1517,7 +1314,7 @@ class ostatus { "href" => $siteinfo["image"], "type" => $imgdata["mime"], "length" => intval($imgdata["size"])); - xml::add_element($doc, $root, "link", "", $attributes); + XML::add_element($doc, $root, "link", "", $attributes); break; case 'video': $attributes = array("rel" => "enclosure", @@ -1525,7 +1322,7 @@ class ostatus { "type" => "text/html; charset=UTF-8", "length" => "", "title" => $siteinfo["title"]); - xml::add_element($doc, $root, "link", "", $attributes); + XML::add_element($doc, $root, "link", "", $attributes); break; default: break; @@ -1538,7 +1335,7 @@ class ostatus { "type" => $imgdata["mime"], "length" => intval($imgdata["size"])); - xml::add_element($doc, $root, "link", "", $attributes); + XML::add_element($doc, $root, "link", "", $attributes); } $arr = explode('[/attach],', $item['attach']); @@ -1557,7 +1354,7 @@ class ostatus { if (trim($matches[4]) != "") { $attributes["title"] = trim($matches[4]); } - xml::add_element($doc, $root, "link", "", $attributes); + XML::add_element($doc, $root, "link", "", $attributes); } } } @@ -1566,27 +1363,27 @@ class ostatus { /** * @brief Adds the author element to the XML document * - * @param object $doc XML document - * @param array $owner Contact data of the poster + * @param object $doc XML document + * @param array $owner Contact data of the poster * * @return object author element */ - private static 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 (dbm::is_result($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); - xml::add_element($doc, $author, "uri", $owner["url"]); - xml::add_element($doc, $author, "name", $owner["nick"]); - xml::add_element($doc, $author, "email", $owner["addr"]); - xml::add_element($doc, $author, "summary", bbcode($owner["about"], false, false, 7)); + XML::add_element($doc, $author, "id", $owner["url"]); + XML::add_element($doc, $author, "activity:object-type", ACTIVITY_OBJ_PERSON); + XML::add_element($doc, $author, "uri", $owner["url"]); + XML::add_element($doc, $author, "name", $owner["nick"]); + XML::add_element($doc, $author, "email", $owner["addr"]); + XML::add_element($doc, $author, "summary", bbcode($owner["about"], false, false, 7)); $attributes = array("rel" => "alternate", "type" => "text/html", "href" => $owner["url"]); - xml::add_element($doc, $author, "link", "", $attributes); + XML::add_element($doc, $author, "link", "", $attributes); $attributes = array( "rel" => "avatar", @@ -1594,7 +1391,7 @@ class ostatus { "media:width" => 175, "media:height" => 175, "href" => $owner["photo"]); - xml::add_element($doc, $author, "link", "", $attributes); + XML::add_element($doc, $author, "link", "", $attributes); if (isset($owner["thumb"])) { $attributes = array( @@ -1603,34 +1400,34 @@ class ostatus { "media:width" => 80, "media:height" => 80, "href" => $owner["thumb"]); - xml::add_element($doc, $author, "link", "", $attributes); + XML::add_element($doc, $author, "link", "", $attributes); } - xml::add_element($doc, $author, "poco:preferredUsername", $owner["nick"]); - xml::add_element($doc, $author, "poco:displayName", $owner["name"]); - xml::add_element($doc, $author, "poco:note", bbcode($owner["about"], false, false, 7)); + XML::add_element($doc, $author, "poco:preferredUsername", $owner["nick"]); + XML::add_element($doc, $author, "poco:displayName", $owner["name"]); + XML::add_element($doc, $author, "poco:note", bbcode($owner["about"], false, false, 7)); if (trim($owner["location"]) != "") { $element = $doc->createElement("poco:address"); - xml::add_element($doc, $element, "poco:formatted", $owner["location"]); + XML::add_element($doc, $element, "poco:formatted", $owner["location"]); $author->appendChild($element); } if (trim($profile["homepage"]) != "") { $urls = $doc->createElement("poco:urls"); - xml::add_element($doc, $urls, "poco:type", "homepage"); - xml::add_element($doc, $urls, "poco:value", $profile["homepage"]); - xml::add_element($doc, $urls, "poco:primary", "true"); + XML::add_element($doc, $urls, "poco:type", "homepage"); + XML::add_element($doc, $urls, "poco:value", $profile["homepage"]); + XML::add_element($doc, $urls, "poco:primary", "true"); $author->appendChild($urls); } if (count($profile)) { - xml::add_element($doc, $author, "followers", "", array("url" => App::get_baseurl()."/viewcontacts/".$owner["nick"])); - xml::add_element($doc, $author, "statusnet:profile_info", "", array("local_id" => $owner["uid"])); + 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"])); } if ($profile["publish"]) { - xml::add_element($doc, $author, "mastodon:scope", "public"); + XML::add_element($doc, $author, "mastodon:scope", "public"); } return $author; } @@ -1639,8 +1436,7 @@ class ostatus { * @TODO Picture attachments should look like this: * https://status.pirati.ca/attachment/572819 - * - */ + */ /** * @brief Returns the given activity if present - otherwise returns the "post" activity @@ -1649,9 +1445,12 @@ class ostatus { * * @return string activity */ - function construct_verb($item) { - if ($item['verb']) + private static function construct_verb($item) + { + if ($item['verb']) { return $item['verb']; + } + return ACTIVITY_POST; } @@ -1662,7 +1461,8 @@ class ostatus { * * @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; @@ -1671,20 +1471,23 @@ class ostatus { /** * @brief Adds an entry element to the XML document * - * @param object $doc XML document - * @param array $item Data of the item that is to be posted - * @param array $owner Contact data of the poster - * @param bool $toplevel + * @param object $doc XML document + * @param array $item Data of the item that is to be posted + * @param array $owner Contact data of the poster + * @param bool $toplevel optional default false * * @return object Entry element */ - private static 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 != "") + if ($repeated_guid != "") { $xml = self::reshare_entry($doc, $item, $owner, $repeated_guid, $toplevel); + } - if ($xml) + if ($xml) { return $xml; + } if ($item["verb"] == ACTIVITY_LIKE) { return self::like_entry($doc, $item, $owner, $toplevel); @@ -1698,23 +1501,20 @@ class ostatus { /** * @brief Adds a source entry to the XML document * - * @param object $doc XML document - * @param array $contact Array of the contact that is added + * @param object $doc XML document + * @param array $contact Array of the contact that is added * * @return object Source element */ - private static 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"]); - xml::add_element($doc, $source, "link", "", array("rel" => "alternate", - "type" => "text/html", - "href" => $contact["alias"])); - xml::add_element($doc, $source, "link", "", array("rel" => "self", - "type" => "application/atom+xml", - "href" => $contact["poll"])); - xml::add_element($doc, $source, "icon", $contact["photo"]); - xml::add_element($doc, $source, "updated", datetime_convert("UTC","UTC",$contact["success_update"]."+00:00",ATOM_TIME)); + XML::add_element($doc, $source, "id", $contact["poll"]); + XML::add_element($doc, $source, "title", $contact["name"]); + XML::add_element($doc, $source, "link", "", array("rel" => "alternate", "type" => "text/html", "href" => $contact["alias"])); + XML::add_element($doc, $source, "link", "", array("rel" => "self", "type" => "application/atom+xml", "href" => $contact["poll"])); + XML::add_element($doc, $source, "icon", $contact["photo"]); + XML::add_element($doc, $source, "updated", datetime_convert("UTC", "UTC", $contact["success_update"]."+00:00", ATOM_TIME)); return $source; } @@ -1722,43 +1522,51 @@ class ostatus { /** * @brief Fetches contact data from the contact or the gcontact table * - * @param string $url URL of the contact - * @param array $owner Contact data of the poster + * @param string $url URL of the contact + * @param array $owner Contact data of the poster * * @return array Contact array */ - 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 (dbm::is_result($r)) { + 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 (DBM::is_result($r)) { $contact = $r[0]; $contact["uid"] = -1; } - if (!dbm::is_result($r)) { - $r = q("SELECT * FROM `gcontact` WHERE `nurl` = '%s' LIMIT 1", - dbesc(normalise_link($url))); - if (dbm::is_result($r)) { + if (!DBM::is_result($r)) { + $r = q( + "SELECT * FROM `gcontact` WHERE `nurl` = '%s' LIMIT 1", + dbesc(normalise_link($url)) + ); + if (DBM::is_result($r)) { $contact = $r[0]; $contact["uid"] = -1; $contact["success_update"] = $contact["updated"]; } } - if (!dbm::is_result($r)) + if (!DBM::is_result($r)) { $contact = owner; + } if (!isset($contact["poll"])) { - $data = probe_url($url); + $data = Probe::uri($url); $contact["poll"] = $data["poll"]; - if (!$contact["alias"]) + if (!$contact["alias"]) { $contact["alias"] = $data["alias"]; + } } - if (!isset($contact["alias"])) + if (!isset($contact["alias"])) { $contact["alias"] = $contact["url"]; + } return $contact; } @@ -1766,26 +1574,31 @@ class ostatus { /** * @brief Adds an entry element with reshared content * - * @param object $doc XML document - * @param array $item Data of the item that is to be posted - * @param array $owner Contact data of the poster - * @param $repeated_guid - * @param bool $toplevel Is it for en entry element (false) or a feed entry (true)? + * @param object $doc XML document + * @param array $item Data of the item that is to be posted + * @param array $owner Contact data of the poster + * @param $repeated_guid + * @param bool $toplevel Is it for en entry element (false) or a feed entry (true)? * * @return object Entry element */ - private static 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"]) && (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); } $title = self::entry_header($doc, $entry, $owner, $toplevel); - $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 (dbm::is_result($r)) { + $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 (DBM::is_result($r)) { $repeated_item = $r[0]; } else { return false; @@ -1800,7 +1613,7 @@ class ostatus { $as_object = $doc->createElement("activity:object"); - xml::add_element($doc, $as_object, "activity:object-type", NAMESPACE_ACTIVITY_SCHEMA."activity"); + XML::add_element($doc, $as_object, "activity:object-type", NAMESPACE_ACTIVITY_SCHEMA."activity"); self::entry_content($doc, $as_object, $repeated_item, $owner, "", "", false); @@ -1809,7 +1622,7 @@ class ostatus { $as_object2 = $doc->createElement("activity:object"); - xml::add_element($doc, $as_object2, "activity:object-type", self::construct_objecttype($repeated_item)); + XML::add_element($doc, $as_object2, "activity:object-type", self::construct_objecttype($repeated_item)); $title = sprintf("New comment by %s", $contact["nick"]); @@ -1833,15 +1646,15 @@ class ostatus { /** * @brief Adds an entry element with a "like" * - * @param object $doc XML document - * @param array $item Data of the item that is to be posted - * @param array $owner Contact data of the poster - * @param bool $toplevel Is it for en entry element (false) or a feed entry (true)? + * @param object $doc XML document + * @param array $item Data of the item that is to be posted + * @param array $owner Contact data of the poster + * @param bool $toplevel Is it for en entry element (false) or a feed entry (true)? * * @return object Entry element with "like" */ - private static function like_entry($doc, $item, $owner, $toplevel) { - + private static function like_entry($doc, $item, $owner, $toplevel) + { 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); } @@ -1853,11 +1666,14 @@ class ostatus { $as_object = $doc->createElement("activity:object"); - $parent = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d", - dbesc($item["thr-parent"]), intval($item["uid"])); + $parent = q( + "SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d", + dbesc($item["thr-parent"]), + intval($item["uid"]) + ); $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']); - xml::add_element($doc, $as_object, "activity:object-type", self::construct_objecttype($parent[0])); + XML::add_element($doc, $as_object, "activity:object-type", self::construct_objecttype($parent[0])); self::entry_content($doc, $as_object, $parent[0], $owner, "New entry"); @@ -1871,27 +1687,27 @@ class ostatus { /** * @brief Adds the person object element to the XML document * - * @param object $doc XML document - * @param array $owner Contact data of the poster - * @param array $contact Contact data of the target + * @param object $doc XML document + * @param array $owner Contact data of the poster + * @param array $contact Contact data of the target * * @return object author element */ - private static 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); + XML::add_element($doc, $object, "activity:object-type", ACTIVITY_OBJ_PERSON); if ($contact['network'] == NETWORK_PHANTOM) { - xml::add_element($doc, $object, "id", $contact['url']); + XML::add_element($doc, $object, "id", $contact['url']); return $object; } - xml::add_element($doc, $object, "id", $contact["alias"]); - xml::add_element($doc, $object, "title", $contact["nick"]); + XML::add_element($doc, $object, "id", $contact["alias"]); + XML::add_element($doc, $object, "title", $contact["nick"]); $attributes = array("rel" => "alternate", "type" => "text/html", "href" => $contact["url"]); - xml::add_element($doc, $object, "link", "", $attributes); + XML::add_element($doc, $object, "link", "", $attributes); $attributes = array( "rel" => "avatar", @@ -1899,14 +1715,14 @@ class ostatus { "media:width" => 175, "media:height" => 175, "href" => $contact["photo"]); - xml::add_element($doc, $object, "link", "", $attributes); + XML::add_element($doc, $object, "link", "", $attributes); - xml::add_element($doc, $object, "poco:preferredUsername", $contact["nick"]); - xml::add_element($doc, $object, "poco:displayName", $contact["name"]); + XML::add_element($doc, $object, "poco:preferredUsername", $contact["nick"]); + XML::add_element($doc, $object, "poco:displayName", $contact["name"]); if (trim($contact["location"]) != "") { $element = $doc->createElement("poco:address"); - xml::add_element($doc, $element, "poco:formatted", $contact["location"]); + XML::add_element($doc, $element, "poco:formatted", $contact["location"]); $object->appendChild($element); } @@ -1916,15 +1732,15 @@ class ostatus { /** * @brief Adds a follow/unfollow entry element * - * @param object $doc XML document - * @param array $item Data of the follow/unfollow message - * @param array $owner Contact data of the poster - * @param bool $toplevel Is it for en entry element (false) or a feed entry (true)? + * @param object $doc XML document + * @param array $item Data of the follow/unfollow message + * @param array $owner Contact data of the poster + * @param bool $toplevel Is it for en entry element (false) or a feed entry (true)? * * @return object Entry element */ - private static 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"); $item["private"] = true; @@ -1937,10 +1753,13 @@ class ostatus { $item['follow'] = $contact['alias']; } - $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s'", - intval($owner['uid']), dbesc(normalise_link($contact["url"]))); + $r = q( + "SELECT `id` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s'", + intval($owner['uid']), + dbesc(normalise_link($contact["url"])) + ); - if (dbm::is_result($r)) { + if (DBM::is_result($r)) { $connect_id = $r[0]['id']; } else { $connect_id = 0; @@ -1956,8 +1775,8 @@ class ostatus { $action = "unfollow"; } - $item["uri"] = $item['parent-uri'] = $item['thr-parent'] = - 'tag:'.get_app()->get_hostname(). + $item["uri"] = $item['parent-uri'] = $item['thr-parent'] + = 'tag:'.get_app()->get_hostname(). ','.date('Y-m-d').':'.$action.':'.$owner['uid']. ':person:'.$connect_id.':'.$item['created']; @@ -1978,22 +1797,22 @@ class ostatus { /** * @brief Adds a regular entry element * - * @param object $doc XML document - * @param array $item Data of the item that is to be posted - * @param array $owner Contact data of the poster - * @param bool $toplevel Is it for en entry element (false) or a feed entry (true)? + * @param object $doc XML document + * @param array $item Data of the item that is to be posted + * @param array $owner Contact data of the poster + * @param bool $toplevel Is it for en entry element (false) or a feed entry (true)? * * @return object Entry element */ - private static function note_entry($doc, $item, $owner, $toplevel) { - + private static function note_entry($doc, $item, $owner, $toplevel) + { 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); } $title = self::entry_header($doc, $entry, $owner, $toplevel); - xml::add_element($doc, $entry, "activity:object-type", ACTIVITY_OBJ_NOTE); + XML::add_element($doc, $entry, "activity:object-type", ACTIVITY_OBJ_NOTE); self::entry_content($doc, $entry, $item, $owner, $title); @@ -2005,14 +1824,15 @@ class ostatus { /** * @brief Adds a header element to the XML document * - * @param object $doc XML document - * @param object $entry The entry element where the elements are added - * @param array $owner Contact data of the poster - * @param bool $toplevel Is it for en entry element (false) or a feed entry (true)? + * @param object $doc XML document + * @param object $entry The entry element where the elements are added + * @param array $owner Contact data of the poster + * @param bool $toplevel Is it for en entry element (false) or a feed entry (true)? * * @return string The title for the element */ - private static 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"); @@ -2040,63 +1860,69 @@ class ostatus { /** * @brief Adds elements to the XML document * - * @param object $doc XML document - * @param object $entry Entry element where the content is added - * @param array $item Data of the item that is to be posted - * @param array $owner Contact data of the poster - * @param string $title Title for the post - * @param string $verb The activity verb - * @param bool $complete Add the "status_net" element? + * @param object $doc XML document + * @param object $entry Entry element where the content is added + * @param array $item Data of the item that is to be posted + * @param array $owner Contact data of the poster + * @param string $title Title for the post + * @param string $verb The activity verb + * @param bool $complete Add the "status_net" element? */ - private static function entry_content($doc, $entry, $item, $owner, $title, $verb = "", $complete = true) { - - if ($verb == "") + 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, "id", $item["uri"]); - xml::add_element($doc, $entry, "title", $title); + XML::add_element($doc, $entry, "id", $item["uri"]); + XML::add_element($doc, $entry, "title", $title); $body = self::format_picture_post($item['body']); - if ($item['title'] != "") + if ($item['title'] != "") { $body = "[b]".$item['title']."[/b]\n\n".$body; + } $body = bbcode($body, false, false, 7); - xml::add_element($doc, $entry, "content", $body, array("type" => "html")); + 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"])); + XML::add_element($doc, $entry, "link", "", array("rel" => "alternate", "type" => "text/html", + "href" => System::baseUrl()."/display/".$item["guid"]) + ); - if ($complete && ($item["id"] > 0)) - xml::add_element($doc, $entry, "status_net", "", array("notice_id" => $item["id"])); + 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); + XML::add_element($doc, $entry, "activity:verb", $verb); - xml::add_element($doc, $entry, "published", datetime_convert("UTC","UTC",$item["created"]."+00:00",ATOM_TIME)); - xml::add_element($doc, $entry, "updated", datetime_convert("UTC","UTC",$item["edited"]."+00:00",ATOM_TIME)); + XML::add_element($doc, $entry, "published", datetime_convert("UTC", "UTC", $item["created"]."+00:00", ATOM_TIME)); + XML::add_element($doc, $entry, "updated", datetime_convert("UTC", "UTC", $item["edited"]."+00:00", ATOM_TIME)); } /** * @brief Adds the elements at the foot of an entry to the XML document * - * @param object $doc XML document - * @param object $entry The entry element where the elements are added - * @param array $item Data of the item that is to be posted - * @param array $owner Contact data of the poster - * @param $complete + * @param object $doc XML document + * @param object $entry The entry element where the elements are added + * @param array $item Data of the item that is to be posted + * @param array $owner Contact data of the poster + * @param bool $complete default true */ - private static 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']) || ($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']); - $thrparent = q("SELECT `guid`, `author-link`, `owner-link`, `plink` FROM `item` WHERE `uid` = %d AND `uri` = '%s'", - intval($owner["uid"]), - dbesc($parent_item)); + $thrparent = q( + "SELECT `guid`, `author-link`, `owner-link`, `plink` FROM `item` WHERE `uid` = %d AND `uri` = '%s'", + intval($owner["uid"]), + dbesc($parent_item) + ); if ($thrparent) { $mentioned[$thrparent[0]["author-link"]] = $thrparent[0]["author-link"]; $mentioned[$thrparent[0]["owner-link"]] = $thrparent[0]["owner-link"]; @@ -2104,27 +1930,27 @@ class ostatus { } 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( "ref" => $parent_item, "href" => $parent_plink); - xml::add_element($doc, $entry, "thr:in-reply-to", "", $attributes); + XML::add_element($doc, $entry, "thr:in-reply-to", "", $attributes); $attributes = array( "rel" => "related", "href" => $parent_plink); - xml::add_element($doc, $entry, "link", "", $attributes); + XML::add_element($doc, $entry, "link", "", $attributes); } 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)) { $r = dba::fetch_first("SELECT `conversation-uri`, `conversation-href` FROM `conversation` WHERE `item-uri` = ?", $parent_item); - if (dbm::is_result($r)) { + if (DBM::is_result($r)) { if ($r['conversation-uri'] != '') { $conversation_uri = $r['conversation-uri']; } @@ -2134,89 +1960,108 @@ class ostatus { } } - xml::add_element($doc, $entry, "link", "", array("rel" => "ostatus:conversation", "href" => $conversation_href)); + XML::add_element($doc, $entry, "link", "", array("rel" => "ostatus:conversation", "href" => $conversation_href)); $attributes = array( "href" => $conversation_href, "local_id" => $item["parent"], "ref" => $conversation_uri); - xml::add_element($doc, $entry, "ostatus:conversation", $conversation_uri, $attributes); + XML::add_element($doc, $entry, "ostatus:conversation", $conversation_uri, $attributes); } $tags = item_getfeedtags($item); - if (count($tags)) - foreach ($tags as $t) - if ($t[0] == "@") + if (count($tags)) { + foreach ($tags as $t) { + if ($t[0] == "@") { $mentioned[$t[1]] = $t[1]; + } + } + } // Make sure that mentions are accepted (GNU Social has problems with mixing HTTP and HTTPS) $newmentions = array(); - foreach ($mentioned AS $mention) { + foreach ($mentioned as $mention) { $newmentions[str_replace("http://", "https://", $mention)] = str_replace("http://", "https://", $mention); $newmentions[str_replace("https://", "http://", $mention)] = str_replace("https://", "http://", $mention); } $mentioned = $newmentions; - foreach ($mentioned AS $mention) { - $r = q("SELECT `forum`, `prv` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s'", + foreach ($mentioned as $mention) { + $r = q( + "SELECT `forum`, `prv` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s'", intval($owner["uid"]), - dbesc(normalise_link($mention))); - if ($r[0]["forum"] || $r[0]["prv"]) - xml::add_element($doc, $entry, "link", "", array("rel" => "mentioned", - "ostatus:object-type" => ACTIVITY_OBJ_GROUP, - "href" => $mention)); - else - xml::add_element($doc, $entry, "link", "", array("rel" => "mentioned", - "ostatus:object-type" => ACTIVITY_OBJ_PERSON, - "href" => $mention)); + dbesc(normalise_link($mention)) + ); + if ($r[0]["forum"] || $r[0]["prv"]) { + XML::add_element($doc, $entry, "link", "", + array( + "rel" => "mentioned", + "ostatus:object-type" => ACTIVITY_OBJ_GROUP, + "href" => $mention) + ); + } else { + XML::add_element($doc, $entry, "link", "", + array( + "rel" => "mentioned", + "ostatus:object-type" => ACTIVITY_OBJ_PERSON, + "href" => $mention) + ); + } } if (!$item["private"]) { - xml::add_element($doc, $entry, "link", "", array("rel" => "ostatus:attention", + XML::add_element($doc, $entry, "link", "", array("rel" => "ostatus:attention", "href" => "http://activityschema.org/collection/public")); - xml::add_element($doc, $entry, "link", "", array("rel" => "mentioned", + XML::add_element($doc, $entry, "link", "", array("rel" => "mentioned", "ostatus:object-type" => "http://activitystrea.ms/schema/1.0/collection", "href" => "http://activityschema.org/collection/public")); - xml::add_element($doc, $entry, "mastodon:scope", "public"); + XML::add_element($doc, $entry, "mastodon:scope", "public"); } - if (count($tags)) - foreach ($tags as $t) - if ($t[0] != "@") - xml::add_element($doc, $entry, "category", "", array("term" => $t[2])); + 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 && ($item["id"] > 0)) { $app = $item["app"]; - if ($app == "") + if ($app == "") { $app = "web"; + } $attributes = array("local_id" => $item["id"], "source" => $app); - if (isset($parent["id"])) + if (isset($parent["id"])) { $attributes["repeat_of"] = $parent["id"]; + } - if ($item["coord"] != "") - xml::add_element($doc, $entry, "georss:point", $item["coord"]); + if ($item["coord"] != "") { + XML::add_element($doc, $entry, "georss:point", $item["coord"]); + } - xml::add_element($doc, $entry, "statusnet:notice_info", "", $attributes); + XML::add_element($doc, $entry, "statusnet:notice_info", "", $attributes); } } /** * @brief Creates the XML feed for a given nickname * - * @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 + * @param object $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, $max_items = 300) { + public static function feed(App $a, $owner_nick, &$last_update, $max_items = 300) + { $stamp = microtime(true); $cachekey = "ostatus:feed:".$owner_nick.":".$last_update; @@ -2230,11 +2075,13 @@ class ostatus { return $result['feed']; } - $r = q("SELECT `contact`.*, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags` + $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 (!dbm::is_result($r)) { + dbesc($owner_nick) + ); + if (!DBM::is_result($r)) { return; } @@ -2244,26 +2091,28 @@ class ostatus { $last_update = 'now -30 days'; } - $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s'); + $check_date = datetime_convert('UTC', 'UTC', $last_update, 'Y-m-d H:i:s'); $authorid = get_contact($owner["url"], 0); - $items = q("SELECT `item`.*, `item`.`id` AS `item_id` FROM `item` USE INDEX (`uid_contactid_created`) + $items = q( + "SELECT `item`.*, `item`.`id` AS `item_id` FROM `item` USE INDEX (`uid_contactid_created`) STRAIGHT_JOIN `thread` ON `thread`.`iid` = `item`.`parent` WHERE `item`.`uid` = %d AND `item`.`contact-id` = %d AND `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 %d", - intval($owner["uid"]), intval($owner["id"]), - intval($authorid), dbesc($check_date), - dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN), intval($max_items)); + intval($owner["uid"]), intval($owner["id"]), + intval($authorid), dbesc($check_date), + dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN), intval($max_items) + ); $doc = new DOMDocument('1.0', 'utf-8'); $doc->formatOutput = true; $root = self::add_header($doc, $owner); - foreach ($items AS $item) { + foreach ($items as $item) { if (Config::get('system', 'ostatus_debug')) { $item['body'] .= '🍼'; } @@ -2288,13 +2137,13 @@ class ostatus { /** * @brief Creates the XML for a salmon message * - * @param array $item Data of the item that is to be posted + * @param array $item Data of the item that is to be posted * @param array $owner Contact data of the poster * * @return string XML for the salmon */ - public static function salmon($item,$owner) { - + public static function salmon($item, $owner) + { $doc = new DOMDocument('1.0', 'utf-8'); $doc->formatOutput = true;