Friendica Communications Platform (please note that this is a clone of the repository at github, issues are handled there) https://friendi.ca
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

844 lines
24 KiB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
10 years ago
10 years ago
11 years ago
10 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
10 years ago
10 years ago
10 years ago
11 years ago
11 years ago
10 years ago
  1. <?php
  2. require_once('library/HTML5/Parser.php');
  3. require_once('include/crypto.php');
  4. if(! function_exists('scrape_dfrn')) {
  5. function scrape_dfrn($url) {
  6. $a = get_app();
  7. $ret = array();
  8. logger('scrape_dfrn: url=' . $url);
  9. $s = fetch_url($url);
  10. if(! $s)
  11. return $ret;
  12. $headers = $a->get_curl_headers();
  13. logger('scrape_dfrn: headers=' . $headers, LOGGER_DEBUG);
  14. $lines = explode("\n",$headers);
  15. if(count($lines)) {
  16. foreach($lines as $line) {
  17. // don't try and run feeds through the html5 parser
  18. if(stristr($line,'content-type:') && ((stristr($line,'application/atom+xml')) || (stristr($line,'application/rss+xml'))))
  19. return ret;
  20. }
  21. }
  22. try {
  23. $dom = HTML5_Parser::parse($s);
  24. } catch (DOMException $e) {
  25. logger('scrape_dfrn: parse error: ' . $e);
  26. }
  27. if(! $dom)
  28. return $ret;
  29. $items = $dom->getElementsByTagName('link');
  30. // get DFRN link elements
  31. foreach($items as $item) {
  32. $x = $item->getAttribute('rel');
  33. if(($x === 'alternate') && ($item->getAttribute('type') === 'application/atom+xml'))
  34. $ret['feed_atom'] = $item->getAttribute('href');
  35. if(substr($x,0,5) == "dfrn-") {
  36. $ret[$x] = $item->getAttribute('href');
  37. }
  38. if($x === 'lrdd') {
  39. $decoded = urldecode($item->getAttribute('href'));
  40. if(preg_match('/acct:([^@]*)@/',$decoded,$matches))
  41. $ret['nick'] = $matches[1];
  42. }
  43. }
  44. // Pull out hCard profile elements
  45. $largest_photo = 0;
  46. $items = $dom->getElementsByTagName('*');
  47. foreach($items as $item) {
  48. if(attribute_contains($item->getAttribute('class'), 'vcard')) {
  49. $level2 = $item->getElementsByTagName('*');
  50. foreach($level2 as $x) {
  51. if(attribute_contains($x->getAttribute('class'),'fn')) {
  52. $ret['fn'] = $x->textContent;
  53. }
  54. if((attribute_contains($x->getAttribute('class'),'photo'))
  55. || (attribute_contains($x->getAttribute('class'),'avatar'))) {
  56. $size = intval($x->getAttribute('width'));
  57. // dfrn prefers 175, so if we find this, we set largest_size so it can't be topped.
  58. if(($size > $largest_photo) || ($size == 175) || (! $largest_photo)) {
  59. $ret['photo'] = $x->getAttribute('src');
  60. $largest_photo = (($size == 175) ? 9999 : $size);
  61. }
  62. }
  63. if(attribute_contains($x->getAttribute('class'),'key')) {
  64. $ret['key'] = $x->textContent;
  65. }
  66. }
  67. }
  68. }
  69. return $ret;
  70. }}
  71. if(! function_exists('validate_dfrn')) {
  72. function validate_dfrn($a) {
  73. $errors = 0;
  74. if(! x($a,'key'))
  75. $errors ++;
  76. if(! x($a,'dfrn-request'))
  77. $errors ++;
  78. if(! x($a,'dfrn-confirm'))
  79. $errors ++;
  80. if(! x($a,'dfrn-notify'))
  81. $errors ++;
  82. if(! x($a,'dfrn-poll'))
  83. $errors ++;
  84. return $errors;
  85. }}
  86. if(! function_exists('scrape_meta')) {
  87. function scrape_meta($url) {
  88. $a = get_app();
  89. $ret = array();
  90. logger('scrape_meta: url=' . $url);
  91. $s = fetch_url($url);
  92. if(! $s)
  93. return $ret;
  94. $headers = $a->get_curl_headers();
  95. logger('scrape_meta: headers=' . $headers, LOGGER_DEBUG);
  96. $lines = explode("\n",$headers);
  97. if(count($lines)) {
  98. foreach($lines as $line) {
  99. // don't try and run feeds through the html5 parser
  100. if(stristr($line,'content-type:') && ((stristr($line,'application/atom+xml')) || (stristr($line,'application/rss+xml'))))
  101. return ret;
  102. }
  103. }
  104. try {
  105. $dom = HTML5_Parser::parse($s);
  106. } catch (DOMException $e) {
  107. logger('scrape_meta: parse error: ' . $e);
  108. }
  109. if(! $dom)
  110. return $ret;
  111. $items = $dom->getElementsByTagName('meta');
  112. // get DFRN link elements
  113. foreach($items as $item) {
  114. $x = $item->getAttribute('name');
  115. if(substr($x,0,5) == "dfrn-")
  116. $ret[$x] = $item->getAttribute('content');
  117. }
  118. return $ret;
  119. }}
  120. if(! function_exists('scrape_vcard')) {
  121. function scrape_vcard($url) {
  122. $a = get_app();
  123. $ret = array();
  124. logger('scrape_vcard: url=' . $url);
  125. $s = fetch_url($url);
  126. if(! $s)
  127. return $ret;
  128. $headers = $a->get_curl_headers();
  129. $lines = explode("\n",$headers);
  130. if(count($lines)) {
  131. foreach($lines as $line) {
  132. // don't try and run feeds through the html5 parser
  133. if(stristr($line,'content-type:') && ((stristr($line,'application/atom+xml')) || (stristr($line,'application/rss+xml'))))
  134. return ret;
  135. }
  136. }
  137. try {
  138. $dom = HTML5_Parser::parse($s);
  139. } catch (DOMException $e) {
  140. logger('scrape_vcard: parse error: ' . $e);
  141. }
  142. if(! $dom)
  143. return $ret;
  144. // Pull out hCard profile elements
  145. $largest_photo = 0;
  146. $items = $dom->getElementsByTagName('*');
  147. foreach($items as $item) {
  148. if(attribute_contains($item->getAttribute('class'), 'vcard')) {
  149. $level2 = $item->getElementsByTagName('*');
  150. foreach($level2 as $x) {
  151. if(attribute_contains($x->getAttribute('class'),'fn'))
  152. $ret['fn'] = $x->textContent;
  153. if((attribute_contains($x->getAttribute('class'),'photo'))
  154. || (attribute_contains($x->getAttribute('class'),'avatar'))) {
  155. $size = intval($x->getAttribute('width'));
  156. if(($size > $largest_photo) || (! $largest_photo)) {
  157. $ret['photo'] = $x->getAttribute('src');
  158. $largest_photo = $size;
  159. }
  160. }
  161. if((attribute_contains($x->getAttribute('class'),'nickname'))
  162. || (attribute_contains($x->getAttribute('class'),'uid'))) {
  163. $ret['nick'] = $x->textContent;
  164. }
  165. }
  166. }
  167. }
  168. return $ret;
  169. }}
  170. if(! function_exists('scrape_feed')) {
  171. function scrape_feed($url) {
  172. $a = get_app();
  173. $ret = array();
  174. $s = fetch_url($url);
  175. $headers = $a->get_curl_headers();
  176. $code = $a->get_curl_code();
  177. logger('scrape_feed: returns: ' . $code . ' headers=' . $headers, LOGGER_DEBUG);
  178. if(! $s) {
  179. logger('scrape_feed: no data returned for ' . $url);
  180. return $ret;
  181. }
  182. $lines = explode("\n",$headers);
  183. if(count($lines)) {
  184. foreach($lines as $line) {
  185. if(stristr($line,'content-type:')) {
  186. if(stristr($line,'application/atom+xml') || stristr($s,'<feed')) {
  187. $ret['feed_atom'] = $url;
  188. return $ret;
  189. }
  190. if(stristr($line,'application/rss+xml') || stristr($s,'<rss')) {
  191. $ret['feed_rss'] = $url;
  192. return $ret;
  193. }
  194. }
  195. }
  196. // perhaps an RSS version 1 feed with a generic or incorrect content-type?
  197. if(stristr($s,'</item>')) {
  198. $ret['feed_rss'] = $url;
  199. return $ret;
  200. }
  201. }
  202. $basename = implode('/', array_slice(explode('/',$url),0,3)) . '/';
  203. $doc = new DOMDocument();
  204. @$doc->loadHTML($s);
  205. $xpath = new DomXPath($doc);
  206. $base = $xpath->query("//base");
  207. foreach ($base as $node) {
  208. $attr = array();
  209. if ($node->attributes->length)
  210. foreach ($node->attributes as $attribute)
  211. $attr[$attribute->name] = $attribute->value;
  212. if ($attr["href"] != "")
  213. $basename = $attr["href"] ;
  214. }
  215. $list = $xpath->query("//link");
  216. foreach ($list as $node) {
  217. $attr = array();
  218. if ($node->attributes->length)
  219. foreach ($node->attributes as $attribute)
  220. $attr[$attribute->name] = $attribute->value;
  221. if (($attr["rel"] == "alternate") AND ($attr["type"] == "application/atom+xml"))
  222. $ret["feed_atom"] = $attr["href"];
  223. if (($attr["rel"] == "alternate") AND ($attr["type"] == "application/rss+xml"))
  224. $ret["feed_rss"] = $attr["href"];
  225. }
  226. // Drupal and perhaps others only provide relative URLs. Turn them into absolute.
  227. if(x($ret,'feed_atom') && (! strstr($ret['feed_atom'],'://')))
  228. $ret['feed_atom'] = $basename . $ret['feed_atom'];
  229. if(x($ret,'feed_rss') && (! strstr($ret['feed_rss'],'://')))
  230. $ret['feed_rss'] = $basename . $ret['feed_rss'];
  231. return $ret;
  232. }}
  233. /**
  234. *
  235. * Probe a network address to discover what kind of protocols we need to communicate with it.
  236. *
  237. * Warning: this function is a bit touchy and there are some subtle dependencies within the logic flow.
  238. * Edit with care.
  239. *
  240. */
  241. /**
  242. *
  243. * PROBE_DIASPORA has a bias towards returning Diaspora information
  244. * while PROBE_NORMAL has a bias towards dfrn/zot - in the case where
  245. * an address (such as a Friendica address) supports more than one type
  246. * of network.
  247. *
  248. */
  249. define ( 'PROBE_NORMAL', 0);
  250. define ( 'PROBE_DIASPORA', 1);
  251. function probe_url($url, $mode = PROBE_NORMAL, $level = 1) {
  252. require_once('include/email.php');
  253. $result = array();
  254. if(! $url)
  255. return $result;
  256. $result = Cache::get("probe_url:".$mode.":".$url);
  257. if (!is_null($result)) {
  258. $result = unserialize($result);
  259. return $result;
  260. }
  261. $network = null;
  262. $diaspora = false;
  263. $diaspora_base = '';
  264. $diaspora_guid = '';
  265. $diaspora_key = '';
  266. $has_lrdd = false;
  267. $email_conversant = false;
  268. $connectornetworks = false;
  269. $appnet = false;
  270. if (strpos($url,'twitter.com')) {
  271. $connectornetworks = true;
  272. $network = NETWORK_TWITTER;
  273. }
  274. // Twitter is deactivated since twitter closed its old API
  275. //$twitter = ((strpos($url,'twitter.com') !== false) ? true : false);
  276. $lastfm = ((strpos($url,'last.fm/user') !== false) ? true : false);
  277. $at_addr = ((strpos($url,'@') !== false) ? true : false);
  278. if((!$appnet) && (!$lastfm) && !$connectornetworks) {
  279. if(strpos($url,'mailto:') !== false && $at_addr) {
  280. $url = str_replace('mailto:','',$url);
  281. $links = array();
  282. }
  283. else
  284. $links = lrdd($url);
  285. if(count($links)) {
  286. $has_lrdd = true;
  287. logger('probe_url: found lrdd links: ' . print_r($links,true), LOGGER_DATA);
  288. foreach($links as $link) {
  289. if($link['@attributes']['rel'] === NAMESPACE_ZOT)
  290. $zot = unamp($link['@attributes']['href']);
  291. if($link['@attributes']['rel'] === NAMESPACE_DFRN)
  292. $dfrn = unamp($link['@attributes']['href']);
  293. if($link['@attributes']['rel'] === 'salmon')
  294. $notify = unamp($link['@attributes']['href']);
  295. if($link['@attributes']['rel'] === NAMESPACE_FEED)
  296. $poll = unamp($link['@attributes']['href']);
  297. if($link['@attributes']['rel'] === 'http://microformats.org/profile/hcard')
  298. $hcard = unamp($link['@attributes']['href']);
  299. if($link['@attributes']['rel'] === 'http://webfinger.net/rel/profile-page')
  300. $profile = unamp($link['@attributes']['href']);
  301. if($link['@attributes']['rel'] === 'http://portablecontacts.net/spec/1.0')
  302. $poco = unamp($link['@attributes']['href']);
  303. if($link['@attributes']['rel'] === 'http://joindiaspora.com/seed_location') {
  304. $diaspora_base = unamp($link['@attributes']['href']);
  305. $diaspora = true;
  306. }
  307. if($link['@attributes']['rel'] === 'http://joindiaspora.com/guid') {
  308. $diaspora_guid = unamp($link['@attributes']['href']);
  309. $diaspora = true;
  310. }
  311. if($link['@attributes']['rel'] === 'diaspora-public-key') {
  312. $diaspora_key = base64_decode(unamp($link['@attributes']['href']));
  313. if(strstr($diaspora_key,'RSA '))
  314. $pubkey = rsatopem($diaspora_key);
  315. else
  316. $pubkey = $diaspora_key;
  317. $diaspora = true;
  318. }
  319. if($link['@attributes']['rel'] === 'http://ostatus.org/schema/1.0/subscribe') {
  320. $diaspora = false;
  321. }
  322. }
  323. // Status.Net can have more than one profile URL. We need to match the profile URL
  324. // to a contact on incoming messages to prevent spam, and we won't know which one
  325. // to match. So in case of two, one of them is stored as an alias. Only store URL's
  326. // and not webfinger user@host aliases. If they've got more than two non-email style
  327. // aliases, let's hope we're lucky and get one that matches the feed author-uri because
  328. // otherwise we're screwed.
  329. foreach($links as $link) {
  330. if($link['@attributes']['rel'] === 'alias') {
  331. if(strpos($link['@attributes']['href'],'@') === false) {
  332. if(isset($profile)) {
  333. if($link['@attributes']['href'] !== $profile)
  334. $alias = unamp($link['@attributes']['href']);
  335. }
  336. else
  337. $profile = unamp($link['@attributes']['href']);
  338. }
  339. }
  340. }
  341. // If the profile is different from the url then the url is abviously an alias
  342. if (($alias == "") AND ($profile != "") AND !$at_addr AND (normalise_link($profile) != normalise_link($url)))
  343. $alias = $url;
  344. }
  345. elseif($mode == PROBE_NORMAL) {
  346. // Check email
  347. $orig_url = $url;
  348. if((strpos($orig_url,'@')) && validate_email($orig_url)) {
  349. $x = q("SELECT `prvkey` FROM `user` WHERE `uid` = %d LIMIT 1",
  350. intval(local_user())
  351. );
  352. $r = q("SELECT * FROM `mailacct` WHERE `uid` = %d AND `server` != '' LIMIT 1",
  353. intval(local_user())
  354. );
  355. if(count($x) && count($r)) {
  356. $mailbox = construct_mailbox_name($r[0]);
  357. $password = '';
  358. openssl_private_decrypt(hex2bin($r[0]['pass']),$password,$x[0]['prvkey']);
  359. $mbox = email_connect($mailbox,$r[0]['user'],$password);
  360. if(! $mbox)
  361. logger('probe_url: email_connect failed.');
  362. unset($password);
  363. }
  364. if($mbox) {
  365. $msgs = email_poll($mbox,$orig_url);
  366. logger('probe_url: searching ' . $orig_url . ', ' . count($msgs) . ' messages found.', LOGGER_DEBUG);
  367. if(count($msgs)) {
  368. $addr = $orig_url;
  369. $network = NETWORK_MAIL;
  370. $name = substr($url,0,strpos($url,'@'));
  371. $phost = substr($url,strpos($url,'@')+1);
  372. $profile = 'http://' . $phost;
  373. // fix nick character range
  374. $vcard = array('fn' => $name, 'nick' => $name, 'photo' => avatar_img($url));
  375. $notify = 'smtp ' . random_string();
  376. $poll = 'email ' . random_string();
  377. $priority = 0;
  378. $x = email_msg_meta($mbox,$msgs[0]);
  379. if(stristr($x[0]->from,$orig_url))
  380. $adr = imap_rfc822_parse_adrlist($x[0]->from,'');
  381. elseif(stristr($x[0]->to,$orig_url))
  382. $adr = imap_rfc822_parse_adrlist($x[0]->to,'');
  383. if(isset($adr)) {
  384. foreach($adr as $feadr) {
  385. if((strcasecmp($feadr->mailbox,$name) == 0)
  386. &&(strcasecmp($feadr->host,$phost) == 0)
  387. && (strlen($feadr->personal))) {
  388. $personal = imap_mime_header_decode($feadr->personal);
  389. $vcard['fn'] = "";
  390. foreach($personal as $perspart)
  391. if ($perspart->charset != "default")
  392. $vcard['fn'] .= iconv($perspart->charset, 'UTF-8//IGNORE', $perspart->text);
  393. else
  394. $vcard['fn'] .= $perspart->text;
  395. $vcard['fn'] = notags($vcard['fn']);
  396. }
  397. }
  398. }
  399. }
  400. imap_close($mbox);
  401. }
  402. }
  403. }
  404. }
  405. if($mode == PROBE_NORMAL) {
  406. if(strlen($zot)) {
  407. $s = fetch_url($zot);
  408. if($s) {
  409. $j = json_decode($s);
  410. if($j) {
  411. $network = NETWORK_ZOT;
  412. $vcard = array(
  413. 'fn' => $j->fullname,
  414. 'nick' => $j->nickname,
  415. 'photo' => $j->photo
  416. );
  417. $profile = $j->url;
  418. $notify = $j->post;
  419. $pubkey = $j->pubkey;
  420. $poll = 'N/A';
  421. }
  422. }
  423. }
  424. if(strlen($dfrn)) {
  425. $ret = scrape_dfrn(($hcard) ? $hcard : $dfrn);
  426. if(is_array($ret) && x($ret,'dfrn-request')) {
  427. $network = NETWORK_DFRN;
  428. $request = $ret['dfrn-request'];
  429. $confirm = $ret['dfrn-confirm'];
  430. $notify = $ret['dfrn-notify'];
  431. $poll = $ret['dfrn-poll'];
  432. $vcard = array();
  433. $vcard['fn'] = $ret['fn'];
  434. $vcard['nick'] = $ret['nick'];
  435. $vcard['photo'] = $ret['photo'];
  436. }
  437. }
  438. }
  439. if($diaspora && $diaspora_base && $diaspora_guid) {
  440. if($mode == PROBE_DIASPORA || ! $notify) {
  441. $notify = $diaspora_base . 'receive/users/' . $diaspora_guid;
  442. $batch = $diaspora_base . 'receive/public' ;
  443. }
  444. if(strpos($url,'@'))
  445. $addr = str_replace('acct:', '', $url);
  446. }
  447. if($network !== NETWORK_ZOT && $network !== NETWORK_DFRN && $network !== NETWORK_MAIL) {
  448. if($diaspora)
  449. $network = NETWORK_DIASPORA;
  450. elseif($has_lrdd)
  451. $network = NETWORK_OSTATUS;
  452. if(strpos($url,'@'))
  453. $addr = str_replace('acct:', '', $url);
  454. $priority = 0;
  455. if($hcard && ! $vcard) {
  456. $vcard = scrape_vcard($hcard);
  457. // Google doesn't use absolute url in profile photos
  458. if((x($vcard,'photo')) && substr($vcard['photo'],0,1) == '/') {
  459. $h = @parse_url($hcard);
  460. if($h)
  461. $vcard['photo'] = $h['scheme'] . '://' . $h['host'] . $vcard['photo'];
  462. }
  463. logger('probe_url: scrape_vcard: ' . print_r($vcard,true), LOGGER_DATA);
  464. }
  465. if($diaspora && $addr) {
  466. // Diaspora returns the name as the nick. As the nick will never be updated,
  467. // let's use the Diaspora nickname (the first part of the handle) as the nick instead
  468. $addr_parts = explode('@', $addr);
  469. $vcard['nick'] = $addr_parts[0];
  470. }
  471. /* if($twitter) {
  472. logger('twitter: setup');
  473. $tid = basename($url);
  474. $tapi = 'https://api.twitter.com/1/statuses/user_timeline.rss';
  475. if(intval($tid))
  476. $poll = $tapi . '?user_id=' . $tid;
  477. else
  478. $poll = $tapi . '?screen_name=' . $tid;
  479. $profile = 'http://twitter.com/#!/' . $tid;
  480. //$vcard['photo'] = 'https://api.twitter.com/1/users/profile_image/' . $tid;
  481. $vcard['photo'] = 'https://api.twitter.com/1/users/profile_image?screen_name=' . $tid . '&size=bigger';
  482. $vcard['nick'] = $tid;
  483. $vcard['fn'] = $tid;
  484. } */
  485. if($lastfm) {
  486. $profile = $url;
  487. $poll = str_replace(array('www.','last.fm/'),array('','ws.audioscrobbler.com/1.0/'),$url) . '/recenttracks.rss';
  488. $vcard['nick'] = basename($url);
  489. $vcard['fn'] = $vcard['nick'] . t(' on Last.fm');
  490. $network = NETWORK_FEED;
  491. }
  492. if(! x($vcard,'fn'))
  493. if(x($vcard,'nick'))
  494. $vcard['fn'] = $vcard['nick'];
  495. $check_feed = false;
  496. if(stristr($url,'tumblr.com') && (! stristr($url,'/rss'))) {
  497. $poll = $url . '/rss';
  498. $check_feed = true;
  499. // Will leave it to others to figure out how to grab the avatar, which is on the $url page in the open graph meta links
  500. }
  501. if($appnet || ! $poll)
  502. $check_feed = true;
  503. if((! isset($vcard)) || (! x($vcard,'fn')) || (! $profile))
  504. $check_feed = true;
  505. if(($at_addr) && (! count($links)))
  506. $check_feed = false;
  507. if ($connectornetworks)
  508. $check_feed = false;
  509. if($check_feed) {
  510. $feedret = scrape_feed(($poll) ? $poll : $url);
  511. logger('probe_url: scrape_feed ' . (($poll)? $poll : $url) . ' returns: ' . print_r($feedret,true), LOGGER_DATA);
  512. if(count($feedret) && ($feedret['feed_atom'] || $feedret['feed_rss'])) {
  513. $poll = ((x($feedret,'feed_atom')) ? unamp($feedret['feed_atom']) : unamp($feedret['feed_rss']));
  514. if(! x($vcard))
  515. $vcard = array();
  516. }
  517. if(x($feedret,'photo') && (! x($vcard,'photo')))
  518. $vcard['photo'] = $feedret['photo'];
  519. require_once('library/simplepie/simplepie.inc');
  520. $feed = new SimplePie();
  521. $xml = fetch_url($poll);
  522. logger('probe_url: fetch feed: ' . $poll . ' returns: ' . $xml, LOGGER_DATA);
  523. $a = get_app();
  524. logger('probe_url: scrape_feed: headers: ' . $a->get_curl_headers(), LOGGER_DATA);
  525. // Don't try and parse an empty string
  526. $feed->set_raw_data(($xml) ? $xml : '<?xml version="1.0" encoding="utf-8" ?><xml></xml>');
  527. $feed->init();
  528. if($feed->error())
  529. logger('probe_url: scrape_feed: Error parsing XML: ' . $feed->error());
  530. if(! x($vcard,'photo'))
  531. $vcard['photo'] = $feed->get_image_url();
  532. $author = $feed->get_author();
  533. if($author) {
  534. $vcard['fn'] = unxmlify(trim($author->get_name()));
  535. if(! $vcard['fn'])
  536. $vcard['fn'] = trim(unxmlify($author->get_email()));
  537. if(strpos($vcard['fn'],'@') !== false)
  538. $vcard['fn'] = substr($vcard['fn'],0,strpos($vcard['fn'],'@'));
  539. $email = unxmlify($author->get_email());
  540. if(! $profile && $author->get_link())
  541. $profile = trim(unxmlify($author->get_link()));
  542. if(! $vcard['photo']) {
  543. $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
  544. if($rawtags) {
  545. $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
  546. if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo'))
  547. $vcard['photo'] = $elems['link'][0]['attribs']['']['href'];
  548. }
  549. }
  550. // Fetch fullname via poco:displayName
  551. $pocotags = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
  552. if ($pocotags) {
  553. $elems = $pocotags[0]['child']['http://portablecontacts.net/spec/1.0'];
  554. if (isset($elems["displayName"]))
  555. $vcard['fn'] = $elems["displayName"][0]["data"];
  556. if (isset($elems["preferredUsername"]))
  557. $vcard['nick'] = $elems["preferredUsername"][0]["data"];
  558. }
  559. }
  560. else {
  561. $item = $feed->get_item(0);
  562. if($item) {
  563. $author = $item->get_author();
  564. if($author) {
  565. $vcard['fn'] = trim(unxmlify($author->get_name()));
  566. if(! $vcard['fn'])
  567. $vcard['fn'] = trim(unxmlify($author->get_email()));
  568. if(strpos($vcard['fn'],'@') !== false)
  569. $vcard['fn'] = substr($vcard['fn'],0,strpos($vcard['fn'],'@'));
  570. $email = unxmlify($author->get_email());
  571. if(! $profile && $author->get_link())
  572. $profile = trim(unxmlify($author->get_link()));
  573. }
  574. if(! $vcard['photo']) {
  575. $rawmedia = $item->get_item_tags('http://search.yahoo.com/mrss/','thumbnail');
  576. if($rawmedia && $rawmedia[0]['attribs']['']['url'])
  577. $vcard['photo'] = unxmlify($rawmedia[0]['attribs']['']['url']);
  578. }
  579. if(! $vcard['photo']) {
  580. $rawtags = $item->get_item_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
  581. if($rawtags) {
  582. $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10];
  583. if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo'))
  584. $vcard['photo'] = $elems['link'][0]['attribs']['']['href'];
  585. }
  586. }
  587. }
  588. }
  589. if((! $vcard['photo']) && strlen($email))
  590. $vcard['photo'] = avatar_img($email);
  591. if($poll === $profile)
  592. $lnk = $feed->get_permalink();
  593. if(isset($lnk) && strlen($lnk))
  594. $profile = $lnk;
  595. if(! $network) {
  596. $network = NETWORK_FEED;
  597. // If it is a feed, don't take the author name as feed name
  598. unset($vcard['fn']);
  599. }
  600. if(! (x($vcard,'fn')))
  601. $vcard['fn'] = notags($feed->get_title());
  602. if(! (x($vcard,'fn')))
  603. $vcard['fn'] = notags($feed->get_description());
  604. if(strpos($vcard['fn'],'Twitter / ') !== false) {
  605. $vcard['fn'] = substr($vcard['fn'],strpos($vcard['fn'],'/')+1);
  606. $vcard['fn'] = trim($vcard['fn']);
  607. }
  608. if(! x($vcard,'nick')) {
  609. $vcard['nick'] = strtolower(notags(unxmlify($vcard['fn'])));
  610. if(strpos($vcard['nick'],' '))
  611. $vcard['nick'] = trim(substr($vcard['nick'],0,strpos($vcard['nick'],' ')));
  612. }
  613. if(! $priority)
  614. $priority = 2;
  615. }
  616. }
  617. if(! x($vcard,'photo')) {
  618. $a = get_app();
  619. $vcard['photo'] = $a->get_baseurl() . '/images/person-175.jpg' ;
  620. }
  621. if(! $profile)
  622. $profile = $url;
  623. // No human could be associated with this link, use the URL as the contact name
  624. if(($network === NETWORK_FEED) && ($poll) && (! x($vcard,'fn')))
  625. $vcard['fn'] = $url;
  626. if (($notify != "") AND ($poll != "")) {
  627. $baseurl = matching(normalise_link($notify), normalise_link($poll));
  628. $baseurl2 = matching($baseurl, normalise_link($profile));
  629. if ($baseurl2 != "")
  630. $baseurl = $baseurl2;
  631. }
  632. if (($baseurl == "") AND ($notify != ""))
  633. $baseurl = matching(normalise_link($profile), normalise_link($notify));
  634. if (($baseurl == "") AND ($poll != ""))
  635. $baseurl = matching(normalise_link($profile), normalise_link($poll));
  636. $baseurl = rtrim($baseurl, "/");
  637. $vcard['fn'] = notags($vcard['fn']);
  638. $vcard['nick'] = str_replace(' ','',notags($vcard['nick']));
  639. $result['name'] = $vcard['fn'];
  640. $result['nick'] = $vcard['nick'];
  641. $result['url'] = $profile;
  642. $result['addr'] = $addr;
  643. $result['batch'] = $batch;
  644. $result['notify'] = $notify;
  645. $result['poll'] = $poll;
  646. $result['request'] = $request;
  647. $result['confirm'] = $confirm;
  648. $result['poco'] = $poco;
  649. $result['photo'] = $vcard['photo'];
  650. $result['priority'] = $priority;
  651. $result['network'] = $network;
  652. $result['alias'] = $alias;
  653. $result['pubkey'] = $pubkey;
  654. $result['baseurl'] = $baseurl;
  655. logger('probe_url: ' . print_r($result,true), LOGGER_DEBUG);
  656. if ($level == 1) {
  657. // Trying if it maybe a diaspora account
  658. if (($result['network'] == NETWORK_FEED) OR ($result['addr'] == "")) {
  659. require_once('include/bbcode.php');
  660. $address = GetProfileUsername($url, "", true);
  661. $result2 = probe_url($address, $mode, ++$level);
  662. if ($result2['network'] != "")
  663. $result = $result2;
  664. }
  665. // Maybe it's some non standard GNU Social installation (Single user, subfolder or no uri rewrite)
  666. if (($result['network'] == NETWORK_FEED) AND ($result['baseurl'] != "") AND ($result['nick'] != "")) {
  667. $addr = $result['nick'].'@'.str_replace("http://", "", $result['baseurl']);
  668. $result2 = probe_url($addr, $mode, ++$level);
  669. if (($result2['network'] != "") AND ($result2['network'] != NETWORK_FEED))
  670. $result = $result2;
  671. }
  672. }
  673. // Only store into the cache if the value seems to be valid
  674. if ($result['network'] != NETWORK_FEED)
  675. Cache::set("probe_url:".$mode.":".$url,serialize($result), CACHE_DAY);
  676. return $result;
  677. }
  678. function matching($part1, $part2) {
  679. $len = min(strlen($part1), strlen($part2));
  680. $match = "";
  681. $matching = true;
  682. $i = 0;
  683. while (($i <= $len) AND $matching) {
  684. if (substr($part1, $i, 1) == substr($part2, $i, 1))
  685. $match .= substr($part1, $i, 1);
  686. else
  687. $matching = false;
  688. $i++;
  689. }
  690. return($match);
  691. }