]> git.mxchange.org Git - friendica.git/blob - src/Protocol/PortableContact.php
Remove deprecated code
[friendica.git] / src / Protocol / PortableContact.php
1 <?php
2 /**
3  * @file src/Protocol/PortableContact.php
4  *
5  * @todo Move GNU Social URL schemata (http://server.tld/user/number) to http://server.tld/username
6  * @todo Fetch profile data from profile page for Redmatrix users
7  * @todo Detect if it is a forum
8  */
9
10 namespace Friendica\Protocol;
11
12 use Exception;
13 use Friendica\Content\Text\HTML;
14 use Friendica\Core\Logger;
15 use Friendica\Core\Protocol;
16 use Friendica\Core\Worker;
17 use Friendica\Database\DBA;
18 use Friendica\DI;
19 use Friendica\Model\GContact;
20 use Friendica\Model\GServer;
21 use Friendica\Util\DateTimeFormat;
22 use Friendica\Util\Network;
23 use Friendica\Util\Strings;
24
25 class PortableContact
26 {
27         const DISABLED = 0;
28         const USERS = 1;
29         const USERS_GCONTACTS = 2;
30         const USERS_GCONTACTS_FALLBACK = 3;
31
32         /**
33          * Fetch POCO data
34          *
35          * @param integer $cid  Contact ID
36          * @param integer $uid  User ID
37          * @param integer $zcid Global Contact ID
38          * @param integer $url  POCO address that should be polled
39          *
40          * Given a contact-id (minimum), load the PortableContacts friend list for that contact,
41          * and add the entries to the gcontact (Global Contact) table, or update existing entries
42          * if anything (name or photo) has changed.
43          * We use normalised urls for comparison which ignore http vs https and www.domain vs domain
44          *
45          * Once the global contact is stored add (if necessary) the contact linkage which associates
46          * the given uid, cid to the global contact entry. There can be many uid/cid combinations
47          * pointing to the same global contact id.
48          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
49          */
50         public static function loadWorker($cid, $uid = 0, $zcid = 0, $url = null)
51         {
52                 // Call the function "load" via the worker
53                 Worker::add(PRIORITY_LOW, 'FetchPoCo', (int)$cid, (int)$uid, (int)$zcid, $url);
54         }
55
56         /**
57          * Fetch POCO data from the worker
58          *
59          * @param integer $cid  Contact ID
60          * @param integer $uid  User ID
61          * @param integer $zcid Global Contact ID
62          * @param integer $url  POCO address that should be polled
63          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
64          */
65         public static function load($cid, $uid, $zcid, $url)
66         {
67                 if ($cid) {
68                         if (!$url || !$uid) {
69                                 $contact = DBA::selectFirst('contact', ['poco', 'uid'], ['id' => $cid]);
70                                 if (DBA::isResult($contact)) {
71                                         $url = $contact['poco'];
72                                         $uid = $contact['uid'];
73                                 }
74                         }
75                         if (!$uid) {
76                                 return;
77                         }
78                 }
79
80                 if (!$url) {
81                         return;
82                 }
83
84                 $url = $url . (($uid) ? '/@me/@all?fields=displayName,urls,photos,updated,network,aboutMe,currentLocation,tags,gender,contactType,generation' : '?fields=displayName,urls,photos,updated,network,aboutMe,currentLocation,tags,gender,contactType,generation');
85
86                 Logger::log('load: ' . $url, Logger::DEBUG);
87
88                 $fetchresult = Network::fetchUrlFull($url);
89                 $s = $fetchresult->getBody();
90
91                 Logger::log('load: returns ' . $s, Logger::DATA);
92
93                 Logger::log('load: return code: ' . $fetchresult->getReturnCode(), Logger::DEBUG);
94
95                 if (($fetchresult->getReturnCode() > 299) || (! $s)) {
96                         return;
97                 }
98
99                 $j = json_decode($s, true);
100
101                 Logger::log('load: json: ' . print_r($j, true), Logger::DATA);
102
103                 if (!isset($j['entry'])) {
104                         return;
105                 }
106
107                 $total = 0;
108                 foreach ($j['entry'] as $entry) {
109                         $total ++;
110                         $profile_url = '';
111                         $profile_photo = '';
112                         $connect_url = '';
113                         $name = '';
114                         $network = '';
115                         $updated = DBA::NULL_DATETIME;
116                         $location = '';
117                         $about = '';
118                         $keywords = '';
119                         $gender = '';
120                         $contact_type = -1;
121                         $generation = 0;
122
123                         if (!empty($entry['displayName'])) {
124                                 $name = $entry['displayName'];
125                         }
126
127                         if (isset($entry['urls'])) {
128                                 foreach ($entry['urls'] as $url) {
129                                         if ($url['type'] == 'profile') {
130                                                 $profile_url = $url['value'];
131                                                 continue;
132                                         }
133                                         if ($url['type'] == 'webfinger') {
134                                                 $connect_url = str_replace('acct:', '', $url['value']);
135                                                 continue;
136                                         }
137                                 }
138                         }
139                         if (isset($entry['photos'])) {
140                                 foreach ($entry['photos'] as $photo) {
141                                         if ($photo['type'] == 'profile') {
142                                                 $profile_photo = $photo['value'];
143                                                 continue;
144                                         }
145                                 }
146                         }
147
148                         if (isset($entry['updated'])) {
149                                 $updated = date(DateTimeFormat::MYSQL, strtotime($entry['updated']));
150                         }
151
152                         if (isset($entry['network'])) {
153                                 $network = $entry['network'];
154                         }
155
156                         if (isset($entry['currentLocation'])) {
157                                 $location = $entry['currentLocation'];
158                         }
159
160                         if (isset($entry['aboutMe'])) {
161                                 $about = HTML::toBBCode($entry['aboutMe']);
162                         }
163
164                         if (isset($entry['gender'])) {
165                                 $gender = $entry['gender'];
166                         }
167
168                         if (isset($entry['generation']) && ($entry['generation'] > 0)) {
169                                 $generation = ++$entry['generation'];
170                         }
171
172                         if (isset($entry['tags'])) {
173                                 foreach ($entry['tags'] as $tag) {
174                                         $keywords = implode(", ", $tag);
175                                 }
176                         }
177
178                         if (isset($entry['contactType']) && ($entry['contactType'] >= 0)) {
179                                 $contact_type = $entry['contactType'];
180                         }
181
182                         $gcontact = ["url" => $profile_url,
183                                         "name" => $name,
184                                         "network" => $network,
185                                         "photo" => $profile_photo,
186                                         "about" => $about,
187                                         "location" => $location,
188                                         "gender" => $gender,
189                                         "keywords" => $keywords,
190                                         "connect" => $connect_url,
191                                         "updated" => $updated,
192                                         "contact-type" => $contact_type,
193                                         "generation" => $generation];
194
195                         try {
196                                 $gcontact = GContact::sanitize($gcontact);
197                                 $gcid = GContact::update($gcontact);
198
199                                 GContact::link($gcid, $uid, $cid, $zcid);
200                         } catch (Exception $e) {
201                                 Logger::log($e->getMessage(), Logger::DEBUG);
202                         }
203                 }
204                 Logger::log("load: loaded $total entries", Logger::DEBUG);
205
206                 $condition = ["`cid` = ? AND `uid` = ? AND `zcid` = ? AND `updated` < UTC_TIMESTAMP - INTERVAL 2 DAY", $cid, $uid, $zcid];
207                 DBA::delete('glink', $condition);
208         }
209
210         /**
211          * Returns a list of all known servers
212          * @return array List of server urls
213          * @throws Exception
214          */
215         public static function serverlist()
216         {
217                 $r = q(
218                         "SELECT `url`, `site_name` AS `displayName`, `network`, `platform`, `version` FROM `gserver`
219                         WHERE `network` IN ('%s', '%s', '%s') AND `last_contact` > `last_failure`
220                         ORDER BY `last_contact`
221                         LIMIT 1000",
222                         DBA::escape(Protocol::DFRN),
223                         DBA::escape(Protocol::DIASPORA),
224                         DBA::escape(Protocol::OSTATUS)
225                 );
226
227                 if (!DBA::isResult($r)) {
228                         return false;
229                 }
230
231                 return $r;
232         }
233
234         /**
235          * Fetch server list from remote servers and adds them when they are new.
236          *
237          * @param string $poco URL to the POCO endpoint
238          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
239          */
240         private static function fetchServerlist($poco)
241         {
242                 $curlResult = Network::curl($poco . "/@server");
243
244                 if (!$curlResult->isSuccess()) {
245                         return;
246                 }
247
248                 $serverlist = json_decode($curlResult->getBody(), true);
249
250                 if (!is_array($serverlist)) {
251                         return;
252                 }
253
254                 foreach ($serverlist as $server) {
255                         $server_url = str_replace("/index.php", "", $server['url']);
256
257                         $r = q("SELECT `nurl` FROM `gserver` WHERE `nurl` = '%s'", DBA::escape(Strings::normaliseLink($server_url)));
258
259                         if (!DBA::isResult($r)) {
260                                 Logger::log("Call server check for server ".$server_url, Logger::DEBUG);
261                                 Worker::add(PRIORITY_LOW, 'UpdateGServer', $server_url);
262                         }
263                 }
264         }
265
266         public static function discoverSingleServer($id)
267         {
268                 $server = DBA::selectFirst('gserver', ['poco', 'nurl', 'url', 'network'], ['id' => $id]);
269
270                 if (!DBA::isResult($server)) {
271                         return false;
272                 }
273
274                 // Discover new servers out there (Works from Friendica version 3.5.2)
275                 self::fetchServerlist($server["poco"]);
276
277                 // Fetch all users from the other server
278                 $url = $server["poco"] . "/?fields=displayName,urls,photos,updated,network,aboutMe,currentLocation,tags,gender,contactType,generation";
279
280                 Logger::info("Fetch all users from the server " . $server["url"]);
281
282                 $curlResult = Network::curl($url);
283
284                 if ($curlResult->isSuccess() && !empty($curlResult->getBody())) {
285                         $data = json_decode($curlResult->getBody(), true);
286
287                         if (!empty($data)) {
288                                 self::discoverServer($data, 2);
289                         }
290
291                         if (DI::config()->get('system', 'poco_discovery') >= self::USERS_GCONTACTS) {
292                                 $timeframe = DI::config()->get('system', 'poco_discovery_since');
293
294                                 if ($timeframe == 0) {
295                                         $timeframe = 30;
296                                 }
297
298                                 $updatedSince = date(DateTimeFormat::MYSQL, time() - $timeframe * 86400);
299
300                                 // Fetch all global contacts from the other server (Not working with Redmatrix and Friendica versions before 3.3)
301                                 $url = $server["poco"]."/@global?updatedSince=".$updatedSince."&fields=displayName,urls,photos,updated,network,aboutMe,currentLocation,tags,gender,contactType,generation";
302
303                                 $success = false;
304
305                                 $curlResult = Network::curl($url);
306
307                                 if ($curlResult->isSuccess() && !empty($curlResult->getBody())) {
308                                         Logger::info("Fetch all global contacts from the server " . $server["nurl"]);
309                                         $data = json_decode($curlResult->getBody(), true);
310
311                                         if (!empty($data)) {
312                                                 $success = self::discoverServer($data);
313                                         }
314                                 }
315
316                                 if (!$success && !empty($data) && DI::config()->get('system', 'poco_discovery') >= self::USERS_GCONTACTS_FALLBACK) {
317                                         Logger::info("Fetch contacts from users of the server " . $server["nurl"]);
318                                         self::discoverServerUsers($data, $server);
319                                 }
320                         }
321
322                         $fields = ['last_poco_query' => DateTimeFormat::utcNow()];
323                         DBA::update('gserver', $fields, ['nurl' => $server["nurl"]]);
324
325                         return true;
326                 } else {
327                         // If the server hadn't replied correctly, then force a sanity check
328                         GServer::check($server["url"], $server["network"], true);
329
330                         // If we couldn't reach the server, we will try it some time later
331                         $fields = ['last_poco_query' => DateTimeFormat::utcNow()];
332                         DBA::update('gserver', $fields, ['nurl' => $server["nurl"]]);
333
334                         return false;
335                 }
336         }
337
338         private static function discoverServerUsers(array $data, array $server)
339         {
340                 if (!isset($data['entry'])) {
341                         return;
342                 }
343
344                 foreach ($data['entry'] as $entry) {
345                         $username = '';
346
347                         if (isset($entry['urls'])) {
348                                 foreach ($entry['urls'] as $url) {
349                                         if ($url['type'] == 'profile') {
350                                                 $profile_url = $url['value'];
351                                                 $path_array = explode('/', parse_url($profile_url, PHP_URL_PATH));
352                                                 $username = end($path_array);
353                                         }
354                                 }
355                         }
356
357                         if ($username != '') {
358                                 Logger::log('Fetch contacts for the user ' . $username . ' from the server ' . $server['nurl'], Logger::DEBUG);
359
360                                 // Fetch all contacts from a given user from the other server
361                                 $url = $server['poco'] . '/' . $username . '/?fields=displayName,urls,photos,updated,network,aboutMe,currentLocation,tags,gender,contactType,generation';
362
363                                 $curlResult = Network::curl($url);
364
365                                 if ($curlResult->isSuccess()) {
366                                         $data = json_decode($curlResult->getBody(), true);
367
368                                         if (!empty($data)) {
369                                                 self::discoverServer($data, 3);
370                                         }
371                                 }
372                         }
373                 }
374         }
375
376         private static function discoverServer(array $data, $default_generation = 0)
377         {
378                 if (empty($data['entry'])) {
379                         return false;
380                 }
381
382                 $success = false;
383
384                 foreach ($data['entry'] as $entry) {
385                         $profile_url = '';
386                         $profile_photo = '';
387                         $connect_url = '';
388                         $name = '';
389                         $network = '';
390                         $updated = DBA::NULL_DATETIME;
391                         $location = '';
392                         $about = '';
393                         $keywords = '';
394                         $gender = '';
395                         $contact_type = -1;
396                         $generation = $default_generation;
397
398                         if (!empty($entry['displayName'])) {
399                                 $name = $entry['displayName'];
400                         }
401
402                         if (isset($entry['urls'])) {
403                                 foreach ($entry['urls'] as $url) {
404                                         if ($url['type'] == 'profile') {
405                                                 $profile_url = $url['value'];
406                                                 continue;
407                                         }
408                                         if ($url['type'] == 'webfinger') {
409                                                 $connect_url = str_replace('acct:' , '', $url['value']);
410                                                 continue;
411                                         }
412                                 }
413                         }
414
415                         if (isset($entry['photos'])) {
416                                 foreach ($entry['photos'] as $photo) {
417                                         if ($photo['type'] == 'profile') {
418                                                 $profile_photo = $photo['value'];
419                                                 continue;
420                                         }
421                                 }
422                         }
423
424                         if (isset($entry['updated'])) {
425                                 $updated = date(DateTimeFormat::MYSQL, strtotime($entry['updated']));
426                         }
427
428                         if (isset($entry['network'])) {
429                                 $network = $entry['network'];
430                         }
431
432                         if (isset($entry['currentLocation'])) {
433                                 $location = $entry['currentLocation'];
434                         }
435
436                         if (isset($entry['aboutMe'])) {
437                                 $about = HTML::toBBCode($entry['aboutMe']);
438                         }
439
440                         if (isset($entry['gender'])) {
441                                 $gender = $entry['gender'];
442                         }
443
444                         if (isset($entry['generation']) && ($entry['generation'] > 0)) {
445                                 $generation = ++$entry['generation'];
446                         }
447
448                         if (isset($entry['contactType']) && ($entry['contactType'] >= 0)) {
449                                 $contact_type = $entry['contactType'];
450                         }
451
452                         if (isset($entry['tags'])) {
453                                 foreach ($entry['tags'] as $tag) {
454                                         $keywords = implode(", ", $tag);
455                                 }
456                         }
457
458                         if ($generation > 0) {
459                                 $success = true;
460
461                                 Logger::log("Store profile ".$profile_url, Logger::DEBUG);
462
463                                 $gcontact = ["url" => $profile_url,
464                                                 "name" => $name,
465                                                 "network" => $network,
466                                                 "photo" => $profile_photo,
467                                                 "about" => $about,
468                                                 "location" => $location,
469                                                 "gender" => $gender,
470                                                 "keywords" => $keywords,
471                                                 "connect" => $connect_url,
472                                                 "updated" => $updated,
473                                                 "contact-type" => $contact_type,
474                                                 "generation" => $generation];
475
476                                 try {
477                                         $gcontact = GContact::sanitize($gcontact);
478                                         GContact::update($gcontact);
479                                 } catch (Exception $e) {
480                                         Logger::log($e->getMessage(), Logger::DEBUG);
481                                 }
482
483                                 Logger::log("Done for profile ".$profile_url, Logger::DEBUG);
484                         }
485                 }
486                 return $success;
487         }
488 }