]> git.mxchange.org Git - friendica-addons.git/blob - facebook/facebook.php
Merge branch 'master' of github.com:annando/friendica-addons
[friendica-addons.git] / facebook / facebook.php
1 <?php
2 /**
3  * Name: Facebook Connector
4  * Version: 1.3
5  * Author: Mike Macgirvin <http://macgirvin.com/profile/mike>
6  *         Tobias Hößl <https://github.com/CatoTH/>
7  */
8
9 /**
10  * Installing the Friendica/Facebook connector
11  *
12  * Detailed instructions how to use this plugin can be found at
13  * https://github.com/friendica/friendica/wiki/How-to:-Friendica%E2%80%99s-Facebook-connector
14  *
15  * Vidoes and embeds will not be posted if there is no other content. Links 
16  * and images will be converted to a format suitable for the Facebook API and 
17  * long posts truncated - with a link to view the full post. 
18  *
19  * Facebook contacts will not be able to view private photos, as they are not able to
20  * authenticate to your site to establish identity. We will address this 
21  * in a future release.
22  */
23  
24  /** TODO
25  * - Implement a method for the administrator to delete all configuration data the plugin has created,
26  *   e.g. the app_access_token
27  */
28
29 // Size of maximum post length increased
30 // see http://www.facebook.com/schrep/posts/203969696349811
31 // define('FACEBOOK_MAXPOSTLEN', 420);
32 define('FACEBOOK_MAXPOSTLEN', 63206);
33 define('FACEBOOK_SESSION_ERR_NOTIFICATION_INTERVAL', 259200); // 3 days
34 define('FACEBOOK_DEFAULT_POLL_INTERVAL', 60); // given in minutes
35 define('FACEBOOK_MIN_POLL_INTERVAL', 5);
36 define('FACEBOOK_RTU_ERR_MAIL_AFTER_MINUTES', 180); // 3 hours
37
38 require_once('include/security.php');
39
40 function facebook_install() {
41         register_hook('post_local',       'addon/facebook/facebook.php', 'facebook_post_local');
42         register_hook('notifier_normal',  'addon/facebook/facebook.php', 'facebook_post_hook');
43         register_hook('jot_networks',     'addon/facebook/facebook.php', 'facebook_jot_nets');
44         register_hook('connector_settings',  'addon/facebook/facebook.php', 'facebook_plugin_settings');
45         register_hook('cron',             'addon/facebook/facebook.php', 'facebook_cron');
46         register_hook('enotify',          'addon/facebook/facebook.php', 'facebook_enotify');
47         register_hook('queue_predeliver', 'addon/facebook/facebook.php', 'fb_queue_hook');
48 }
49
50
51 function facebook_uninstall() {
52         unregister_hook('post_local',       'addon/facebook/facebook.php', 'facebook_post_local');
53         unregister_hook('notifier_normal',  'addon/facebook/facebook.php', 'facebook_post_hook');
54         unregister_hook('jot_networks',     'addon/facebook/facebook.php', 'facebook_jot_nets');
55         unregister_hook('connector_settings',  'addon/facebook/facebook.php', 'facebook_plugin_settings');
56         unregister_hook('cron',             'addon/facebook/facebook.php', 'facebook_cron');
57         unregister_hook('enotify',          'addon/facebook/facebook.php', 'facebook_enotify');
58         unregister_hook('queue_predeliver', 'addon/facebook/facebook.php', 'fb_queue_hook');
59
60         // hook moved
61         unregister_hook('post_local_end',  'addon/facebook/facebook.php', 'facebook_post_hook');
62         unregister_hook('plugin_settings',  'addon/facebook/facebook.php', 'facebook_plugin_settings');
63 }
64
65
66 /* declare the facebook_module function so that /facebook url requests will land here */
67
68 function facebook_module() {}
69
70
71
72 // If a->argv[1] is a nickname, this is a callback from Facebook oauth requests.
73 // If $_REQUEST["realtime_cb"] is set, this is a callback from the Real-Time Updates API
74
75 /**
76  * @param App $a
77  */
78 function facebook_init(&$a) {
79
80         if (x($_REQUEST, "realtime_cb") && x($_REQUEST, "realtime_cb")) {
81                 logger("facebook_init: Facebook Real-Time callback called", LOGGER_DEBUG);
82
83                 if (x($_REQUEST, "hub_verify_token")) {
84                         // this is the verification callback while registering for real time updates
85
86                         $verify_token = get_config('facebook', 'cb_verify_token');
87                         if ($verify_token != $_REQUEST["hub_verify_token"]) {
88                                 logger('facebook_init: Wrong Facebook Callback Verifier - expected ' . $verify_token . ', got ' . $_REQUEST["hub_verify_token"]);
89                                 return;
90                         }
91
92                         if (x($_REQUEST, "hub_challenge")) {
93                                 logger('facebook_init: Answering Challenge: ' . $_REQUEST["hub_challenge"], LOGGER_DATA);
94                                 echo $_REQUEST["hub_challenge"];
95                                 die();
96                         }
97                 }
98
99                 require_once('include/items.php');
100
101                 // this is a status update
102                 $content = file_get_contents("php://input");
103                 if (is_numeric($content)) $content = file_get_contents("php://input");
104                 $js = json_decode($content);
105                 logger(print_r($js, true), LOGGER_DATA);
106
107                 if (!isset($js->object) || $js->object != "user" || !isset($js->entry)) {
108                         logger('facebook_init: Could not parse Real-Time Update data', LOGGER_DEBUG);
109                         return;
110                 }
111
112                 $affected_users = array("feed" => array(), "friends" => array());
113
114                 foreach ($js->entry as $entry) {
115                         $fbuser = $entry->uid;
116                         foreach ($entry->changed_fields as $field) {
117                                 if (!isset($affected_users[$field])) {
118                                         logger('facebook_init: Unknown field "' . $field . '"');
119                                         continue;
120                                 }
121                                 if (in_array($fbuser, $affected_users[$field])) continue;
122
123                                 $r = q("SELECT `uid` FROM `pconfig` WHERE `cat` = 'facebook' AND `k` = 'self_id' AND `v` = '%s' LIMIT 1", dbesc($fbuser));
124                                 if(! count($r))
125                                         continue;
126                                 $uid = $r[0]['uid'];
127
128                                 $access_token = get_pconfig($uid,'facebook','access_token');
129                                 if(! $access_token)
130                                         return;
131
132                                 switch ($field) {
133                                         case "feed":
134                                                 logger('facebook_init: FB-User ' . $fbuser . ' / feed', LOGGER_DEBUG);
135
136                                                 if(! get_pconfig($uid,'facebook','no_wall')) {
137                                                         $private_wall = intval(get_pconfig($uid,'facebook','private_wall'));
138                                                         $s = fetch_url('https://graph.facebook.com/me/feed?access_token=' . $access_token);
139                                                         if($s) {
140                                                                 $j = json_decode($s);
141                                                                 if (isset($j->data)) {
142                                                                         logger('facebook_init: wall: ' . print_r($j,true), LOGGER_DATA);
143                                                                         fb_consume_stream($uid,$j,($private_wall) ? false : true);
144                                                                 } else {
145                                                                         logger('facebook_init: wall: got no data from Facebook: ' . print_r($j,true), LOGGER_NORMAL);
146                                                                 }
147                                                         }
148                                                 }
149
150                                         break;
151                                         case "friends":
152                                                 logger('facebook_init: FB-User ' . $fbuser . ' / friends', LOGGER_DEBUG);
153
154                                                 fb_get_friends($uid, false);
155                                                 set_pconfig($uid,'facebook','friend_check',time());
156                                         break;
157                                         default:
158                                                 logger('facebook_init: Unknown callback field for ' . $fbuser, LOGGER_NORMAL);
159                                 }
160                                 $affected_users[$field][] = $fbuser;
161                         }
162                 }
163         }
164
165
166         if($a->argc != 2)
167                 return;
168         $nick = $a->argv[1];
169         if(strlen($nick))
170                 $r = q("SELECT `uid` FROM `user` WHERE `nickname` = '%s' LIMIT 1",
171                                 dbesc($nick)
172                 );
173         if(!(isset($r) && count($r)))
174                 return;
175
176         $uid           = $r[0]['uid'];
177         $auth_code     = (x($_GET, 'code') ? $_GET['code'] : '');
178         $error         = (x($_GET, 'error_description') ? $_GET['error_description'] : '');
179
180
181         if($error)
182                 logger('facebook_init: Error: ' . $error);
183
184         if($auth_code && $uid) {
185
186                 $appid = get_config('facebook','appid');
187                 $appsecret = get_config('facebook', 'appsecret');
188
189                 $x = fetch_url('https://graph.facebook.com/oauth/access_token?client_id='
190                         . $appid . '&client_secret=' . $appsecret . '&redirect_uri='
191                         . urlencode($a->get_baseurl() . '/facebook/' . $nick)
192                         . '&code=' . $auth_code);
193
194                 logger('facebook_init: returned access token: ' . $x, LOGGER_DATA);
195
196                 if(strpos($x,'access_token=') !== false) {
197                         $token = str_replace('access_token=', '', $x);
198                         if(strpos($token,'&') !== false)
199                                 $token = substr($token,0,strpos($token,'&'));
200                         set_pconfig($uid,'facebook','access_token',$token);
201                         set_pconfig($uid,'facebook','post','1');
202                         if(get_pconfig($uid,'facebook','no_linking') === false)
203                                 set_pconfig($uid,'facebook','no_linking',1);
204                         fb_get_self($uid);
205                         fb_get_friends($uid, true);
206                         fb_consume_all($uid);
207
208                 }
209
210         }
211
212 }
213
214
215 /**
216  * @param int $uid
217  */
218 function fb_get_self($uid) {
219         $access_token = get_pconfig($uid,'facebook','access_token');
220         if(! $access_token)
221                 return;
222         $s = fetch_url('https://graph.facebook.com/me/?access_token=' . $access_token);
223         if($s) {
224                 $j = json_decode($s);
225                 set_pconfig($uid,'facebook','self_id',(string) $j->id);
226         }
227 }
228
229 /**
230  * @param int $uid
231  * @param string $access_token
232  * @param array $persons
233  */
234 function fb_get_friends_sync_new($uid, $access_token, $persons) {
235     $persons_todo = array();
236     foreach ($persons as $person) {
237         $link = 'http://facebook.com/profile.php?id=' . $person->id;
238
239         $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `url` = '%s' LIMIT 1",
240             intval($uid),
241             dbesc($link)
242         );
243
244         if (count($r) == 0) {
245             logger('fb_get_friends: new contact found: ' . $link, LOGGER_DEBUG);
246             $persons_todo[] = $person;
247         }
248
249         if (count($persons_todo) > 0) fb_get_friends_sync_full($uid, $access_token, $persons_todo);
250     }
251 }
252
253 /**
254  * @param int $uid
255  * @param object $contact
256  */
257 function fb_get_friends_sync_parsecontact($uid, $contact) {
258     $contact->link = 'http://facebook.com/profile.php?id=' . $contact->id;
259
260     // If its a page then set the first name from the username
261     if (!$contact->first_name and $contact->username)
262         $contact->first_name = $contact->username;
263
264     // check if we already have a contact
265
266     $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `url` = '%s' LIMIT 1",
267         intval($uid),
268         dbesc($contact->link)
269     );
270
271     if(count($r)) {
272
273                 // update profile photos once every two weeks as we have no notification of when they change.
274
275                 $update_photo = (($r[0]['avatar-date'] < datetime_convert('','','now -14 days')) ? true : false);
276
277         // check that we have all the photos, this has been known to fail on occasion
278
279         if((! $r[0]['photo']) || (! $r[0]['thumb']) || (! $r[0]['micro']) || ($update_photo)) {
280             require_once("Photo.php");
281
282             $photos = import_profile_photo('https://graph.facebook.com/' . $contact->id . '/picture', $uid, $r[0]['id']);
283
284             q("UPDATE `contact` SET `photo` = '%s',
285                                         `thumb` = '%s',
286                                         `micro` = '%s',
287                                         `name-date` = '%s',
288                                         `uri-date` = '%s',
289                                         `avatar-date` = '%s'
290                                         WHERE `id` = %d LIMIT 1
291                                 ",
292                 dbesc($photos[0]),
293                 dbesc($photos[1]),
294                 dbesc($photos[2]),
295                 dbesc(datetime_convert()),
296                 dbesc(datetime_convert()),
297                 dbesc(datetime_convert()),
298                 intval($r[0]['id'])
299             );
300         }
301         return;
302     }
303     else {
304
305         // create contact record
306         q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `addr`, `alias`, `notify`, `poll`,
307                                 `name`, `nick`, `photo`, `network`, `rel`, `priority`,
308                                 `writable`, `blocked`, `readonly`, `pending` )
309                                 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, 0, 0, 0 ) ",
310             intval($uid),
311             dbesc(datetime_convert()),
312             dbesc($contact->link),
313             dbesc(normalise_link($contact->link)),
314             dbesc(''),
315             dbesc(''),
316             dbesc($contact->id),
317             dbesc('facebook ' . $contact->id),
318             dbesc($contact->name),
319             dbesc(($contact->nickname) ? $contact->nickname : strtolower($contact->first_name)),
320             dbesc('https://graph.facebook.com/' . $contact->id . '/picture'),
321             dbesc(NETWORK_FACEBOOK),
322             intval(CONTACT_IS_FRIEND),
323             intval(1),
324             intval(1)
325         );
326     }
327
328     $r = q("SELECT * FROM `contact` WHERE `url` = '%s' AND `uid` = %d LIMIT 1",
329         dbesc($contact->link),
330         intval($uid)
331     );
332
333     if(! count($r)) {
334         return;
335     }
336
337     $contact_id  = $r[0]['id'];
338
339         $g = q("select def_gid from user where uid = %d limit 1",
340                 intval($uid)
341         );
342         if($g && intval($g[0]['def_gid'])) {
343                 require_once('include/group.php');
344                 group_add_member($uid,'',$contact_id,$g[0]['def_gid']);
345         }
346
347     require_once("Photo.php");
348
349     $photos = import_profile_photo($r[0]['photo'],$uid,$contact_id);
350
351     q("UPDATE `contact` SET `photo` = '%s',
352                         `thumb` = '%s',
353                         `micro` = '%s',
354                         `name-date` = '%s',
355                         `uri-date` = '%s',
356                         `avatar-date` = '%s'
357                         WHERE `id` = %d LIMIT 1
358                 ",
359         dbesc($photos[0]),
360         dbesc($photos[1]),
361         dbesc($photos[2]),
362         dbesc(datetime_convert()),
363         dbesc(datetime_convert()),
364         dbesc(datetime_convert()),
365         intval($contact_id)
366     );
367 }
368
369 /**
370  * @param int $uid
371  * @param string $access_token
372  * @param array $persons
373  */
374 function fb_get_friends_sync_full($uid, $access_token, $persons) {
375     if (count($persons) == 0) return;
376     $nums = Ceil(count($persons) / 50);
377     for ($i = 0; $i < $nums; $i++) {
378         $batch_request = array();
379         for ($j = $i * 50; $j < ($i+1) * 50 && $j < count($persons); $j++) $batch_request[] = array('method'=>'GET', 'relative_url'=>$persons[$j]->id);
380         $s = post_url('https://graph.facebook.com/', array('access_token' => $access_token, 'batch' => json_encode($batch_request)));
381         if($s) {
382             $results = json_decode($s);
383             logger('fb_get_friends: info: ' . print_r($results,true), LOGGER_DATA);
384             foreach ($results as $contact) {
385                 if ($contact->code != 200) logger('fb_get_friends: not found: ' . print_r($contact,true), LOGGER_DEBUG);
386                 else fb_get_friends_sync_parsecontact($uid, json_decode($contact->body));
387             }
388         }
389     }
390 }
391
392
393
394 // if $fullsync is true, only new contacts are searched for
395
396 /**
397  * @param int $uid
398  * @param bool $fullsync
399  */
400 function fb_get_friends($uid, $fullsync = true) {
401
402         $r = q("SELECT `uid` FROM `user` WHERE `uid` = %d AND `account_expired` = 0 LIMIT 1",
403                 intval($uid)
404         );
405         if(! count($r))
406                 return;
407
408         $access_token = get_pconfig($uid,'facebook','access_token');
409
410         $no_linking = get_pconfig($uid,'facebook','no_linking');
411
412         if($no_linking)
413                 return;
414
415         if(! $access_token)
416                 return;
417         $s = fetch_url('https://graph.facebook.com/me/friends?access_token=' . $access_token);
418         if($s) {
419                 logger('facebook: fb_get_friends: ' . $s, LOGGER_DATA);
420                 $j = json_decode($s);
421                 logger('facebook: fb_get_friends: json: ' . print_r($j,true), LOGGER_DATA);
422                 if(! $j->data)
423                         return;
424
425             $persons_todo = array();
426         foreach($j->data as $person) $persons_todo[] = $person;
427
428         if ($fullsync)
429             fb_get_friends_sync_full($uid, $access_token, $persons_todo);
430         else
431             fb_get_friends_sync_new($uid, $access_token, $persons_todo);
432         }
433 }
434
435 // This is the POST method to the facebook settings page
436 // Content is posted to Facebook in the function facebook_post_hook()
437
438 /**
439  * @param App $a
440  */
441 function facebook_post(&$a) {
442
443         $uid = local_user();
444         if($uid){
445
446
447                 $fb_limited = get_config('facebook','crestrict');
448
449
450                 $value = ((x($_POST,'post_by_default')) ? intval($_POST['post_by_default']) : 0);
451                 set_pconfig($uid,'facebook','post_by_default', $value);
452
453                 $no_linking = get_pconfig($uid,'facebook','no_linking');
454
455                 $no_wall = ((x($_POST,'facebook_no_wall')) ? intval($_POST['facebook_no_wall']) : 0);
456                 set_pconfig($uid,'facebook','no_wall',$no_wall);
457
458                 $private_wall = ((x($_POST,'facebook_private_wall')) ? intval($_POST['facebook_private_wall']) : 0);
459                 set_pconfig($uid,'facebook','private_wall',$private_wall);
460
461
462                 set_pconfig($uid,'facebook','blocked_apps',escape_tags(trim($_POST['blocked_apps'])));
463
464                 $linkvalue = ((x($_POST,'facebook_linking')) ? intval($_POST['facebook_linking']) : 0);
465
466                 if($fb_limited) {
467                         if($linkvalue == 0)
468                                 set_pconfig($uid,'facebook','no_linking', 1);
469                 }
470                 else
471                         set_pconfig($uid,'facebook','no_linking', (($linkvalue) ? 0 : 1));
472
473                 // FB linkage was allowed but has just been turned off - remove all FB contacts and posts
474
475                 if((! intval($no_linking)) && (! intval($linkvalue))) {
476                         $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `network` = '%s' ",
477                                 intval($uid),
478                                 dbesc(NETWORK_FACEBOOK)
479                         );
480                         if(count($r)) {
481                                 require_once('include/Contact.php');
482                                 foreach($r as $rr)
483                                         contact_remove($rr['id']);
484                         }
485                 }
486                 elseif(intval($no_linking) && intval($linkvalue)) {
487                         // FB linkage is now allowed - import stuff.
488                         fb_get_self($uid);
489                         fb_get_friends($uid, true);
490                         fb_consume_all($uid);
491                 }
492
493                 info( t('Settings updated.') . EOL);
494         } 
495
496         return;         
497 }
498
499 // Facebook settings form
500
501 /**
502  * @param App $a
503  * @return string
504  */
505 function facebook_content(&$a) {
506
507         if(! local_user()) {
508                 notice( t('Permission denied.') . EOL);
509                 return '';
510         }
511
512         if($a->argc > 1 && $a->argv[1] === 'remove') {
513                 del_pconfig(local_user(),'facebook','post');
514                 info( t('Facebook disabled') . EOL);
515         }
516
517         if($a->argc > 1 && $a->argv[1] === 'friends') {
518                 fb_get_friends(local_user(), true);
519                 info( t('Updating contacts') . EOL);
520         }
521
522
523         $fb_limited = get_config('facebook','restrict');
524
525         $o = '';
526         
527         $fb_installed = false;
528         if (get_pconfig(local_user(),'facebook','post')) {
529                 $access_token = get_pconfig(local_user(),'facebook','access_token');
530                 if ($access_token) {
531                         $s = fetch_url('https://graph.facebook.com/me/feed?access_token=' . $access_token);
532                         if($s) {
533                                 $j = json_decode($s);
534                                 if (isset($j->data)) $fb_installed = true;
535                         }
536                 }
537         }
538         
539         $appid = get_config('facebook','appid');
540
541         if(! $appid) {
542                 notice( t('Facebook API key is missing.') . EOL);
543                 return '';
544         }
545
546         $a->page['htmlhead'] .= '<link rel="stylesheet" type="text/css" href="'
547                 . $a->get_baseurl() . '/addon/facebook/facebook.css' . '" media="all" />' . "\r\n";
548
549         $o .= '<h3>' . t('Facebook Connect') . '</h3>';
550
551         if(! $fb_installed) { 
552                 $o .= '<div id="facebook-enable-wrapper">';
553
554                 $o .= '<a href="https://www.facebook.com/dialog/oauth?client_id=' . $appid . '&redirect_uri=' 
555                         . $a->get_baseurl() . '/facebook/' . $a->user['nickname'] . '&scope=publish_stream,read_stream,offline_access">' . t('Install Facebook connector for this account.') . '</a>';
556                 $o .= '</div>';
557         }
558
559         if($fb_installed) {
560                 $o .= '<div id="facebook-disable-wrapper">';
561
562                 $o .= '<a href="' . $a->get_baseurl() . '/facebook/remove' . '">' . t('Remove Facebook connector') . '</a></div>';
563
564                 $o .= '<div id="facebook-enable-wrapper">';
565
566                 $o .= '<a href="https://www.facebook.com/dialog/oauth?client_id=' . $appid . '&redirect_uri=' 
567                         . $a->get_baseurl() . '/facebook/' . $a->user['nickname'] . '&scope=publish_stream,read_stream,offline_access">' . t('Re-authenticate [This is necessary whenever your Facebook password is changed.]') . '</a>';
568                 $o .= '</div>';
569         
570                 $o .= '<div id="facebook-post-default-form">';
571                 $o .= '<form action="facebook" method="post" >';
572                 $post_by_default = get_pconfig(local_user(),'facebook','post_by_default');
573                 $checked = (($post_by_default) ? ' checked="checked" ' : '');
574                 $o .= '<input type="checkbox" name="post_by_default" value="1"' . $checked . '/>' . ' ' . t('Post to Facebook by default') . EOL;
575
576                 $no_linking = get_pconfig(local_user(),'facebook','no_linking');
577                 $checked = (($no_linking) ? '' : ' checked="checked" ');
578                 if($fb_limited) {
579                         if($no_linking) {
580                                 $o .= EOL . '<strong>' . t('Facebook friend linking has been disabled on this site. The following settings will have no effect.') . '</strong>' . EOL;
581                                 $checked .= " disabled ";
582                         }
583                         else {
584                                 $o .= EOL . '<strong>' . t('Facebook friend linking has been disabled on this site. If you disable it, you will be unable to re-enable it.') . '</strong>' . EOL;
585                         }
586                 }
587                 $o .= '<input type="checkbox" name="facebook_linking" value="1"' . $checked . '/>' . ' ' . t('Link all your Facebook friends and conversations on this website') . EOL ;
588
589                 $o .= '<p>' . t('Facebook conversations consist of your <em>profile wall</em> and your friend <em>stream</em>.');
590                 $o .= ' ' . t('On this website, your Facebook friend stream is only visible to you.');
591                 $o .= ' ' . t('The following settings determine the privacy of your Facebook profile wall on this website.') . '</p>';
592
593                 $private_wall = get_pconfig(local_user(),'facebook','private_wall');
594                 $checked = (($private_wall) ? ' checked="checked" ' : '');
595                 $o .= '<input type="checkbox" name="facebook_private_wall" value="1"' . $checked . '/>' . ' ' . t('On this website your Facebook profile wall conversations will only be visible to you') . EOL ;
596
597
598                 $no_wall = get_pconfig(local_user(),'facebook','no_wall');
599                 $checked = (($no_wall) ? ' checked="checked" ' : '');
600                 $o .= '<input type="checkbox" name="facebook_no_wall" value="1"' . $checked . '/>' . ' ' . t('Do not import your Facebook profile wall conversations') . EOL ;
601
602                 $o .= '<p>' . t('If you choose to link conversations and leave both of these boxes unchecked, your Facebook profile wall will be merged with your profile wall on this website and your privacy settings on this website will be used to determine who may see the conversations.') . '</p>';
603
604
605                 $blocked_apps = get_pconfig(local_user(),'facebook','blocked_apps');
606
607                 $o .= '<div><label id="blocked-apps-label" for="blocked-apps">' . t('Comma separated applications to ignore') . ' </label></div>';
608         $o .= '<div><textarea id="blocked-apps" name="blocked_apps" >' . htmlspecialchars($blocked_apps) . '</textarea></div>';
609
610                 $o .= '<input type="submit" name="submit" value="' . t('Submit') . '" /></form></div>';
611         }
612
613         return $o;
614 }
615
616
617 /**
618  * @param App $a
619  * @param null|object $b
620  * @return mixed
621  */
622 function facebook_cron($a,$b) {
623
624         $last = get_config('facebook','last_poll');
625         
626         $poll_interval = intval(get_config('facebook','poll_interval'));
627         if(! $poll_interval)
628                 $poll_interval = FACEBOOK_DEFAULT_POLL_INTERVAL;
629
630         if($last) {
631                 $next = $last + ($poll_interval * 60);
632                 if($next > time()) 
633                         return;
634         }
635
636         logger('facebook_cron');
637
638
639         // Find the FB users on this site and randomize in case one of them
640         // uses an obscene amount of memory. It may kill this queue run
641         // but hopefully we'll get a few others through on each run. 
642
643         $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'facebook' AND `k` = 'post' AND `v` = '1' ORDER BY RAND() ");
644         if(count($r)) {
645                 foreach($r as $rr) {
646                         if(get_pconfig($rr['uid'],'facebook','no_linking'))
647                                 continue;
648                         $ab = intval(get_config('system','account_abandon_days'));
649                         if($ab > 0) {
650                                 $z = q("SELECT `uid` FROM `user` WHERE `uid` = %d AND `login_date` > UTC_TIMESTAMP() - INTERVAL %d DAY LIMIT 1",
651                                         intval($rr['uid']),
652                                         intval($ab)
653                                 );
654                                 if(! count($z))
655                                         continue;
656                         }
657
658                         // check for new friends once a day
659                         $last_friend_check = get_pconfig($rr['uid'],'facebook','friend_check');
660                         if($last_friend_check) 
661                                 $next_friend_check = $last_friend_check + 86400;
662                         else
663                             $next_friend_check = 0;
664                         if($next_friend_check <= time()) {
665                                 fb_get_friends($rr['uid'], true);
666                                 set_pconfig($rr['uid'],'facebook','friend_check',time());
667                         }
668                         fb_consume_all($rr['uid']);
669                 }
670         }
671         
672         if (get_config('facebook', 'realtime_active') == 1) {
673                 if (!facebook_check_realtime_active()) {
674                         
675                         logger('facebook_cron: Facebook is not sending Real-Time Updates any more, although it is supposed to. Trying to fix it...', LOGGER_NORMAL);
676                         facebook_subscription_add_users();
677                         
678                         if (facebook_check_realtime_active()) 
679                                 logger('facebook_cron: Successful', LOGGER_NORMAL);
680                         else {
681                                 logger('facebook_cron: Failed', LOGGER_NORMAL);
682
683                                 $first_err = get_config('facebook', 'realtime_first_err');
684                                 if (!$first_err) {
685                                         $first_err = time();
686                                         set_config('facebook', 'realtime_first_err', $first_err);
687                                 }
688                                 $first_err_ago = (time() - $first_err);
689
690                                 if(strlen($a->config['admin_email']) && !get_config('facebook', 'realtime_err_mailsent') && $first_err_ago > (FACEBOOK_RTU_ERR_MAIL_AFTER_MINUTES * 60)) {
691                                         mail($a->config['admin_email'], t('Problems with Facebook Real-Time Updates'),
692                                                 "Hi!\n\nThere's a problem with the Facebook Real-Time Updates that cannot be solved automatically. Maybe a permission issue?\n\nPlease try to re-activate it on " . $a->config["system"]["url"] . "/admin/plugins/facebook\n\nThis e-mail will only be sent once.",
693                                                 'From: ' . t('Administrator') . '@' . $_SERVER['SERVER_NAME'] . "\n"
694                                                 . 'Content-type: text/plain; charset=UTF-8' . "\n"
695                                                 . 'Content-transfer-encoding: 8bit'
696                                         );
697                                         
698                                         set_config('facebook', 'realtime_err_mailsent', 1);
699                                 }
700                         }
701                 } else { // !facebook_check_realtime_active()
702                         del_config('facebook', 'realtime_err_mailsent');
703                         del_config('facebook', 'realtime_first_err');
704                 }
705         }
706         
707         set_config('facebook','last_poll', time());
708
709 }
710
711
712 /**
713  * @param App $a
714  * @param null|object $b
715  */
716 function facebook_plugin_settings(&$a,&$b) {
717
718         $b .= '<div class="settings-block">';
719         $b .= '<h3>' . t('Facebook') . '</h3>';
720         $b .= '<a href="facebook">' . t('Facebook Connector Settings') . '</a><br />';
721         $b .= '</div>';
722
723 }
724
725
726 /**
727  * @param App $a
728  * @param null|object $o
729  */
730 function facebook_plugin_admin(&$a, &$o){
731
732
733         $o = '<input type="hidden" name="form_security_token" value="' . get_form_security_token("fbsave") . '">';
734         
735         $o .= '<h4>' . t('Facebook API Key') . '</h4>';
736         
737         $appid  = get_config('facebook', 'appid'  );
738         $appsecret = get_config('facebook', 'appsecret' );
739         $poll_interval = get_config('facebook', 'poll_interval' );
740         $sync_comments = get_config('facebook', 'sync_comments' );
741         if (!$poll_interval) $poll_interval = FACEBOOK_DEFAULT_POLL_INTERVAL;
742         
743         $ret1 = q("SELECT `v` FROM `config` WHERE `cat` = 'facebook' AND `k` = 'appid' LIMIT 1");
744         $ret2 = q("SELECT `v` FROM `config` WHERE `cat` = 'facebook' AND `k` = 'appsecret' LIMIT 1");
745         if ((count($ret1) > 0 && $ret1[0]['v'] != $appid) || (count($ret2) > 0 && $ret2[0]['v'] != $appsecret)) $o .= t('Error: it appears that you have specified the App-ID and -Secret in your .htconfig.php file. As long as they are specified there, they cannot be set using this form.<br><br>');
746         
747         $working_connection = false;
748         if ($appid && $appsecret) {
749                 $subs = facebook_subscriptions_get();
750                 if ($subs === null) $o .= t('Error: the given API Key seems to be incorrect (the application access token could not be retrieved).') . '<br>';
751                 elseif (is_array($subs)) {
752                         $o .= t('The given API Key seems to work correctly.') . '<br>';
753                         $working_connection = true;
754                 } else $o .= t('The correctness of the API Key could not be detected. Somthing strange\'s going on.') . '<br>';
755         }
756         
757         $o .= '<label for="fb_appid">' . t('App-ID / API-Key') . '</label><input id="fb_appid" name="appid" type="text" value="' . escape_tags($appid ? $appid : "") . '"><br style="clear: both;">';
758         $o .= '<label for="fb_appsecret">' . t('Application secret') . '</label><input id="fb_appsecret" name="appsecret" type="text" value="' . escape_tags($appsecret ? $appsecret : "") . '"><br style="clear: both;">';
759         $o .= '<label for="fb_poll_interval">' . sprintf(t('Polling Interval in minutes (minimum %1$s minutes)'), FACEBOOK_MIN_POLL_INTERVAL) . '</label><input name="poll_interval" id="fb_poll_interval" type="number" min="' . FACEBOOK_MIN_POLL_INTERVAL . '" value="' . $poll_interval . '"><br style="clear: both;">';
760         $o .= '<label for="fb_sync_comments">' . t('Synchronize comments (no comments on Facebook are missed, at the cost of increased system load)') . '</label><input name="sync_comments" id="fb_sync_comments" type="checkbox" ' . ($sync_comments ? 'checked' : '') . '><br style="clear: both;">';
761         $o .= '<input type="submit" name="fb_save_keys" value="' . t('Save') . '">';
762         
763         if ($working_connection) {
764                 $o .= '<h4>' . t('Real-Time Updates') . '</h4>';
765                 
766                 $activated = facebook_check_realtime_active();
767                 if ($activated) {
768                         $o .= t('Real-Time Updates are activated.') . '<br><br>';
769                         $o .= '<input type="submit" name="real_time_deactivate" value="' . t('Deactivate Real-Time Updates') . '">';
770                 } else {
771                         $o .= t('Real-Time Updates not activated.') . '<br><input type="submit" name="real_time_activate" value="' . t('Activate Real-Time Updates') . '">';
772                 }
773         }
774 }
775
776 /**
777  * @param App $a
778  */
779
780 function facebook_plugin_admin_post(&$a){
781         check_form_security_token_redirectOnErr('/admin/plugins/facebook', 'fbsave');
782         
783         if (x($_REQUEST,'fb_save_keys')) {
784                 set_config('facebook', 'appid', $_REQUEST['appid']);
785                 set_config('facebook', 'appsecret', $_REQUEST['appsecret']);
786                 $poll_interval = IntVal($_REQUEST['poll_interval']);
787                 if ($poll_interval >= FACEBOOK_MIN_POLL_INTERVAL) set_config('facebook', 'poll_interval', $poll_interval);
788                 set_config('facebook', 'sync_comments', (x($_REQUEST, 'sync_comments') ? 1 : 0));
789                 del_config('facebook', 'app_access_token');
790                 info(t('The new values have been saved.'));
791         }
792         if (x($_REQUEST,'real_time_activate')) {
793                 facebook_subscription_add_users();
794         }
795         if (x($_REQUEST,'real_time_deactivate')) {
796                 facebook_subscription_del_users();
797         }
798 }
799
800 /**
801  * @param App $a
802  * @param object $b
803  * @return mixed
804  */
805 function facebook_jot_nets(&$a,&$b) {
806         if(! local_user())
807                 return;
808
809         $fb_post = get_pconfig(local_user(),'facebook','post');
810         if(intval($fb_post) == 1) {
811                 $fb_defpost = get_pconfig(local_user(),'facebook','post_by_default');
812                 $selected = ((intval($fb_defpost) == 1) ? ' checked="checked" ' : '');
813                 $b .= '<div class="profile-jot-net"><input type="checkbox" name="facebook_enable"' . $selected . ' value="1" /> ' 
814                         . t('Post to Facebook') . '</div>';     
815         }
816 }
817
818
819 /**
820  * @param App $a
821  * @param object $b
822  * @return mixed
823  */
824 function facebook_post_hook(&$a,&$b) {
825
826
827         if($b['deleted'] || ($b['created'] !== $b['edited']))
828                 return;
829
830         /**
831          * Post to Facebook stream
832          */
833
834         require_once('include/group.php');
835         require_once('include/html2plain.php');
836
837         logger('Facebook post');
838
839         $reply = false;
840         $likes = false;
841
842         $deny_arr = array();
843         $allow_arr = array();
844
845         $toplevel = (($b['id'] == $b['parent']) ? true : false);
846
847
848         $linking = ((get_pconfig($b['uid'],'facebook','no_linking')) ? 0 : 1);
849
850         if((! $toplevel) && ($linking)) {
851                 $r = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
852                         intval($b['parent']),
853                         intval($b['uid'])
854                 );
855                 if(count($r) && substr($r[0]['uri'],0,4) === 'fb::')
856                         $reply = substr($r[0]['uri'],4);
857                 elseif(count($r) && substr($r[0]['extid'],0,4) === 'fb::')
858                         $reply = substr($r[0]['extid'],4);
859                 else
860                         return;
861
862                 $u = q("SELECT * FROM user where uid = %d limit 1",
863                         intval($b['uid'])
864                 );
865                 if(! count($u))
866                         return;
867
868                 // only accept comments from the item owner. Other contacts are unknown to FB.
869  
870                 if(! link_compare($b['author-link'], $a->get_baseurl() . '/profile/' . $u[0]['nickname']))
871                         return;
872                 
873
874                 logger('facebook reply id=' . $reply);
875         }
876
877         if(strstr($b['postopts'],'facebook') || ($b['private']) || ($reply)) {
878
879                 if($b['private'] && $reply === false) {
880                         $allow_people = expand_acl($b['allow_cid']);
881                         $allow_groups = expand_groups(expand_acl($b['allow_gid']));
882                         $deny_people  = expand_acl($b['deny_cid']);
883                         $deny_groups  = expand_groups(expand_acl($b['deny_gid']));
884
885                         $recipients = array_unique(array_merge($allow_people,$allow_groups));
886                         $deny = array_unique(array_merge($deny_people,$deny_groups));
887
888                         $allow_str = dbesc(implode(', ',$recipients));
889                         if($allow_str) {
890                                 $r = q("SELECT `notify` FROM `contact` WHERE `id` IN ( $allow_str ) AND `network` = 'face'"); 
891                                 if(count($r))
892                                         foreach($r as $rr)
893                                                 $allow_arr[] = $rr['notify'];
894                         }
895
896                         $deny_str = dbesc(implode(', ',$deny));
897                         if($deny_str) {
898                                 $r = q("SELECT `notify` FROM `contact` WHERE `id` IN ( $deny_str ) AND `network` = 'face'"); 
899                                 if(count($r))
900                                         foreach($r as $rr)
901                                                 $deny_arr[] = $rr['notify'];
902                         }
903
904                         if(count($deny_arr) && (! count($allow_arr))) {
905
906                                 // One or more FB folks were denied access but nobody on FB was specifically allowed access.
907                                 // This might cause the post to be open to public on Facebook, but only to selected members
908                                 // on another network. Since this could potentially leak a post to somebody who was denied, 
909                                 // we will skip posting it to Facebook with a slightly vague but relevant message that will 
910                                 // hopefully lead somebody to this code comment for a better explanation of what went wrong.
911
912                                 notice( t('Post to Facebook cancelled because of multi-network access permission conflict.') . EOL);
913                                 return;
914                         }
915
916
917                         // if it's a private message but no Facebook members are allowed or denied, skip Facebook post
918
919                         if((! count($allow_arr)) && (! count($deny_arr)))
920                                 return;
921                 }
922
923                 if($b['verb'] == ACTIVITY_LIKE)
924                         $likes = true;                          
925
926
927                 $appid  = get_config('facebook', 'appid'  );
928                 $secret = get_config('facebook', 'appsecret' );
929
930                 if($appid && $secret) {
931
932                         logger('facebook: have appid+secret');
933
934                         $fb_token  = get_pconfig($b['uid'],'facebook','access_token');
935
936
937                         // post to facebook if it's a public post and we've ticked the 'post to Facebook' box, 
938                         // or it's a private message with facebook participants
939                         // or it's a reply or likes action to an existing facebook post                 
940
941                         if($fb_token && ($toplevel || $b['private'] || $reply)) {
942                                 logger('facebook: able to post');
943                                 require_once('library/facebook.php');
944                                 require_once('include/bbcode.php');
945
946                                 $msg = $b['body'];
947
948                                 logger('Facebook post: original msg=' . $msg, LOGGER_DATA);
949
950                                 // make links readable before we strip the code
951
952                                 // unless it's a dislike - just send the text as a comment
953
954                                 // if($b['verb'] == ACTIVITY_DISLIKE)
955                                 //      $msg = trim(strip_tags(bbcode($msg)));
956
957                                 // Old code
958                                 /*$search_str = $a->get_baseurl() . '/search';
959
960                                 if(preg_match("/\[url=(.*?)\](.*?)\[\/url\]/is",$msg,$matches)) {
961
962                                         // don't use hashtags for message link
963
964                                         if(strpos($matches[2],$search_str) === false) {
965                                                 $link = $matches[1];
966                                                 if(substr($matches[2],0,5) != '[img]')
967                                                         $linkname = $matches[2];
968                                         }
969                                 }
970
971                                 // strip tag links to avoid link clutter, this really should be 
972                                 // configurable because we're losing information
973
974                                 $msg = preg_replace("/\#\[url=(.*?)\](.*?)\[\/url\]/is",'#$2',$msg);
975
976                                 // provide the link separately for normal links
977                                 $msg = preg_replace("/\[url=(.*?)\](.*?)\[\/url\]/is",'$2 $1',$msg);
978
979                                 if(preg_match("/\[img\](.*?)\[\/img\]/is",$msg,$matches))
980                                         $image = $matches[1];
981
982                                 $msg = preg_replace("/\[img\](.*?)\[\/img\]/is", t('Image: ') . '$1', $msg);
983
984                                 if((strpos($link,z_root()) !== false) && (! $image))
985                                         $image = $a->get_baseurl() . '/images/friendica-64.jpg';
986
987                                 $msg = trim(strip_tags(bbcode($msg)));*/
988
989                                 // New code
990
991                                 // Looking for the first image
992                                 $image = '';
993                                 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/is",$b['body'],$matches))
994                                         $image = $matches[3];
995
996                                 if ($image == '')
997                                         if(preg_match("/\[img\](.*?)\[\/img\]/is",$b['body'],$matches))
998                                                 $image = $matches[1];
999
1000                                 // Checking for a bookmark element
1001                                 $body = $b['body'];
1002                                 if (strpos($body, "[bookmark") !== false) {
1003                                         // splitting the text in two parts:
1004                                         // before and after the bookmark
1005                                         $pos = strpos($body, "[bookmark");
1006                                         $body1 = substr($body, 0, $pos);
1007                                         $body2 = substr($body, $pos);
1008
1009                                         // Removing the bookmark and all quotes after the bookmark
1010                                         // they are mostly only the content after the bookmark.
1011                                         $body2 = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism",'',$body2);
1012                                         $body2 = preg_replace("/\[quote\=([^\]]*)\](.*?)\[\/quote\]/ism",'',$body2);
1013                                         $body2 = preg_replace("/\[quote\](.*?)\[\/quote\]/ism",'',$body2);
1014
1015                                         $body = $body1.$body2;
1016                                 }
1017
1018                                 // At first convert the text to html
1019                                 $html = bbcode($body, false, false);
1020
1021                                 // Then convert it to plain text
1022                                 $msg = trim($b['title']." \n\n".html2plain($html, 0, true));
1023                                 $msg = html_entity_decode($msg,ENT_QUOTES,'UTF-8');
1024
1025                                 // Removing multiple newlines
1026                                 while (strpos($msg, "\n\n\n") !== false)
1027                                         $msg = str_replace("\n\n\n", "\n\n", $msg);
1028
1029                                 // add any attachments as text urls
1030                                 $arr = explode(',',$b['attach']);
1031
1032                                 if(count($arr)) {
1033                                         $msg .= "\n";
1034                                         foreach($arr as $r) {
1035                                                 $matches = false;
1036                                                 $cnt = preg_match('|\[attach\]href=\"(.*?)\" size=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"\[\/attach\]|',$r,$matches);
1037                                                 if($cnt) {
1038                                                         $msg .= "\n".$matches[1];
1039                                                 }
1040                                         }
1041                                 }
1042
1043                                 $link = '';
1044                                 $linkname = '';
1045                                 // look for bookmark-bbcode and handle it with priority
1046                                 if(preg_match("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/is",$b['body'],$matches)) {
1047                                         $link = $matches[1];
1048                                         $linkname = $matches[2];
1049                                 }
1050
1051                                 // If there is no bookmark element then take the first link
1052                                 if ($link == '') {
1053                                         $links = collecturls($html);
1054                                         if (sizeof($links) > 0) {
1055                                                 reset($links);
1056                                                 $link = current($links);
1057                                         }
1058                                 }
1059
1060                                 // Remove trailing and leading spaces
1061                                 $msg = trim($msg);
1062
1063                                 // Since facebook increased the maxpostlen massively this never should happen again :)
1064                                 if (strlen($msg) > FACEBOOK_MAXPOSTLEN) {
1065                                         require_once('library/slinky.php');
1066
1067                                         $display_url = $b['plink'];
1068
1069                                         $slinky = new Slinky( $display_url );
1070                                         // setup a cascade of shortening services
1071                                         // try to get a short link from these services
1072                                         // in the order ur1.ca, trim, id.gd, tinyurl
1073                                         $slinky->set_cascade( array( new Slinky_UR1ca(), new Slinky_Trim(), new Slinky_IsGd(), new Slinky_TinyURL() ) );
1074                                         $shortlink = $slinky->short();
1075                                         // the new message will be shortened such that "... $shortlink"
1076                                         // will fit into the character limit
1077                                         $msg = substr($msg, 0, FACEBOOK_MAXPOSTLEN - strlen($shortlink) - 4);
1078                                         $msg .= '... ' . $shortlink;
1079                                 }
1080
1081                                 // Fallback - if message is empty
1082                                 if(!strlen($msg))
1083                                         $msg = $link;
1084
1085                                 if(!strlen($msg))
1086                                         $msg = $image;
1087
1088                                 if(!strlen($msg))
1089                                         $msg = $linkname;
1090
1091                                 // If there is nothing to post then exit
1092                                 if(!strlen($msg))
1093                                         return;
1094
1095                                 logger('Facebook post: msg=' . $msg, LOGGER_DATA);
1096
1097                                 if($likes) { 
1098                                         $postvars = array('access_token' => $fb_token);
1099                                 }
1100                                 else {
1101                                         $postvars = array(
1102                                                 'access_token' => $fb_token, 
1103                                                 'message' => $msg
1104                                         );
1105                                         if(isset($image)) {
1106                                                 $postvars['picture'] = $image;
1107                                                 //$postvars['type'] = "photo";
1108                                         }
1109                                         if(isset($link)) {
1110                                                 $postvars['link'] = $link;
1111                                                 //$postvars['type'] = "link";
1112                                         }
1113                                         if(isset($linkname))
1114                                                 $postvars['name'] = $linkname;
1115                                 }
1116
1117                                 if(($b['private']) && ($toplevel)) {
1118                                         $postvars['privacy'] = '{"value": "CUSTOM", "friends": "SOME_FRIENDS"';
1119                                         if(count($allow_arr))
1120                                                 $postvars['privacy'] .= ',"allow": "' . implode(',',$allow_arr) . '"';
1121                                         if(count($deny_arr))
1122                                                 $postvars['privacy'] .= ',"deny": "' . implode(',',$deny_arr) . '"';
1123                                         $postvars['privacy'] .= '}';
1124
1125                                 }
1126
1127                                 if($reply) {
1128                                         $url = 'https://graph.facebook.com/' . $reply . '/' . (($likes) ? 'likes' : 'comments');
1129                                 } else if (($link != "")  or ($image != "") or ($b['title'] == '') or (strlen($msg) < 500)) { 
1130                                         $url = 'https://graph.facebook.com/me/feed';
1131                                         if($b['plink'])
1132                                                 $postvars['actions'] = '{"name": "' . t('View on Friendica') . '", "link": "' .  $b['plink'] . '"}';
1133                                 } else {
1134                                         // if its only a message and a subject and the message is larger than 500 characters then post it as note
1135                                         $postvars = array(
1136                                                 'access_token' => $fb_token,
1137                                                 'message' => bbcode($b['body'], false, false),
1138                                                 'subject' => $b['title'],
1139                                         );
1140                                         $url = 'https://graph.facebook.com/me/notes';
1141                                 }
1142
1143                                 logger('facebook: post to ' . $url);
1144                                 logger('facebook: postvars: ' . print_r($postvars,true));
1145
1146                                 // "test_mode" prevents anything from actually being posted.
1147                                 // Otherwise, let's do it.
1148
1149                                 if(! get_config('facebook','test_mode')) {
1150                                         $x = post_url($url, $postvars);
1151                                         logger('Facebook post returns: ' . $x, LOGGER_DEBUG);
1152
1153                                         $retj = json_decode($x);
1154                                         if($retj->id) {
1155                                                 q("UPDATE `item` SET `extid` = '%s' WHERE `id` = %d LIMIT 1",
1156                                                         dbesc('fb::' . $retj->id),
1157                                                         intval($b['id'])
1158                                                 );
1159                                         }
1160                                         else {
1161                                                 if(! $likes) {
1162                                                         $s = serialize(array('url' => $url, 'item' => $b['id'], 'post' => $postvars));
1163                                                         require_once('include/queue_fn.php');
1164                                                         add_to_queue($a->contact,NETWORK_FACEBOOK,$s);
1165                                                         notice( t('Facebook post failed. Queued for retry.') . EOL);
1166                                                 }
1167                                                 
1168                                                 if (isset($retj->error) && $retj->error->type == "OAuthException" && $retj->error->code == 190) {
1169                                                         logger('Facebook session has expired due to changed password.', LOGGER_DEBUG);
1170                                                         
1171                                                         $last_notification = get_pconfig($b['uid'], 'facebook', 'session_expired_mailsent');
1172                                                         if (!$last_notification || $last_notification < (time() - FACEBOOK_SESSION_ERR_NOTIFICATION_INTERVAL)) {
1173                                                                 require_once('include/enotify.php');
1174                                                         
1175                                                                 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", intval($b['uid']) );
1176                                                                 notification(array(
1177                                                                         'uid' => $b['uid'],
1178                                                                         'type' => NOTIFY_SYSTEM,
1179                                                                         'system_type' => 'facebook_connection_invalid',
1180                                                                         'language'     => $r[0]['language'],
1181                                                                         'to_name'      => $r[0]['username'],
1182                                                                         'to_email'     => $r[0]['email'],
1183                                                                         'source_name'  => t('Administrator'),
1184                                                                         'source_link'  => $a->config["system"]["url"],
1185                                                                         'source_photo' => $a->config["system"]["url"] . '/images/person-80.jpg',
1186                                                                 ));
1187                                                                 
1188                                                                 set_pconfig($b['uid'], 'facebook', 'session_expired_mailsent', time());
1189                                                         } else logger('Facebook: No notification, as the last one was sent on ' . $last_notification, LOGGER_DEBUG);
1190                                                 }
1191                                         }
1192                                 }
1193                         }
1194                 }
1195         }
1196 }
1197
1198 /**
1199  * @param App $app
1200  * @param object $data
1201  */
1202 function facebook_enotify(&$app, &$data) {
1203         if (x($data, 'params') && $data['params']['type'] == NOTIFY_SYSTEM && x($data['params'], 'system_type') && $data['params']['system_type'] == 'facebook_connection_invalid') {
1204                 $data['itemlink'] = '/facebook';
1205                 $data['epreamble'] = $data['preamble'] = t('Your Facebook connection became invalid. Please Re-authenticate.');
1206                 $data['subject'] = t('Facebook connection became invalid');
1207                 $data['body'] = sprintf( t("Hi %1\$s,\n\nThe connection between your accounts on %2\$s and Facebook became invalid. This usually happens after you change your Facebook-password. To enable the connection again, you have to %3\$sre-authenticate the Facebook-connector%4\$s."), $data['params']['to_name'], "[url=" . $app->config["system"]["url"] . "]" . $app->config["sitename"] . "[/url]", "[url=" . $app->config["system"]["url"] . "/facebook]", "[/url]");
1208         }
1209 }
1210
1211 /**
1212  * @param App $a
1213  * @param object $b
1214  */
1215 function facebook_post_local(&$a,&$b) {
1216
1217         // Figure out if Facebook posting is enabled for this post and file it in 'postopts'
1218         // where we will discover it during background delivery.
1219
1220         // This can only be triggered by a local user posting to their own wall.
1221
1222         if((local_user()) && (local_user() == $b['uid'])) {
1223
1224                 $fb_post   = intval(get_pconfig(local_user(),'facebook','post'));
1225                 $fb_enable = (($fb_post && x($_REQUEST,'facebook_enable')) ? intval($_REQUEST['facebook_enable']) : 0);
1226
1227                 // if API is used, default to the chosen settings
1228                 if($_REQUEST['api_source'] && intval(get_pconfig(local_user(),'facebook','post_by_default')))
1229                         $fb_enable = 1;
1230
1231                 if(! $fb_enable)
1232                         return;
1233
1234                 if(strlen($b['postopts']))
1235                         $b['postopts'] .= ',';
1236                 $b['postopts'] .= 'facebook';
1237         }
1238 }
1239
1240
1241 /**
1242  * @param App $a
1243  * @param object $b
1244  */
1245 function fb_queue_hook(&$a,&$b) {
1246
1247         $qi = q("SELECT * FROM `queue` WHERE `network` = '%s'",
1248                 dbesc(NETWORK_FACEBOOK)
1249         );
1250         if(! count($qi))
1251                 return;
1252
1253         require_once('include/queue_fn.php');
1254
1255         foreach($qi as $x) {
1256                 if($x['network'] !== NETWORK_FACEBOOK)
1257                         continue;
1258
1259                 logger('facebook_queue: run');
1260
1261                 $r = q("SELECT `user`.* FROM `user` LEFT JOIN `contact` on `contact`.`uid` = `user`.`uid` 
1262                         WHERE `contact`.`self` = 1 AND `contact`.`id` = %d LIMIT 1",
1263                         intval($x['cid'])
1264                 );
1265                 if(! count($r))
1266                         continue;
1267
1268                 $user = $r[0];
1269
1270                 $appid  = get_config('facebook', 'appid'  );
1271                 $secret = get_config('facebook', 'appsecret' );
1272
1273                 if($appid && $secret) {
1274                         $fb_post   = intval(get_pconfig($user['uid'],'facebook','post'));
1275                         $fb_token  = get_pconfig($user['uid'],'facebook','access_token');
1276
1277                         if($fb_post && $fb_token) {
1278                                 logger('facebook_queue: able to post');
1279                                 require_once('library/facebook.php');
1280
1281                                 $z = unserialize($x['content']);
1282                                 $item = $z['item'];
1283                                 $j = post_url($z['url'],$z['post']);
1284
1285                                 $retj = json_decode($j);
1286                                 if($retj->id) {
1287                                         q("UPDATE `item` SET `extid` = '%s' WHERE `id` = %d LIMIT 1",
1288                                                 dbesc('fb::' . $retj->id),
1289                                                 intval($item)
1290                                         );
1291                                         logger('facebook_queue: success: ' . $j); 
1292                                         remove_queue_item($x['id']);
1293                                 }
1294                                 else {
1295                                         logger('facebook_queue: failed: ' . $j);
1296                                         update_queue_time($x['id']);
1297                                 }
1298                         }
1299                 }
1300         }
1301 }
1302
1303 /**
1304  * @param string $access_token
1305  * @param int $since
1306  * @return object
1307  */
1308 function fb_get_timeline($access_token, &$since) {
1309
1310     $entries = new stdClass();
1311         $entries->data = array();
1312         $newest = 0;
1313
1314         $url = 'https://graph.facebook.com/me/home?access_token='.$access_token;
1315
1316         if ($since != 0)
1317                 $url .= "&since=".$since;
1318
1319         do {
1320                 $s = fetch_url($url);
1321                 $j = json_decode($s);
1322                 $oldestdate = time();
1323                 if (isset($j->data))
1324                         foreach ($j->data as $entry) {
1325                                 $created = strtotime($entry->created_time);
1326
1327                                 if ($newest < $created)
1328                                         $newest = $created;
1329
1330                                 if ($created >= $since)
1331                                         $entries->data[] = $entry;
1332
1333                                 if ($created <= $oldestdate)
1334                                         $oldestdate = $created;
1335                         }
1336                 else
1337                         break;
1338
1339                 $url = (isset($j->paging) && isset($j->paging->next) ? $j->paging->next : '');
1340
1341         } while (($oldestdate > $since) and ($since != 0) and ($url != ''));
1342
1343         if ($newest > $since)
1344                 $since = $newest;
1345
1346         return($entries);
1347 }
1348
1349 /**
1350  * @param int $uid
1351  */
1352 function fb_consume_all($uid) {
1353
1354         require_once('include/items.php');
1355
1356         $access_token = get_pconfig($uid,'facebook','access_token');
1357         if(! $access_token)
1358                 return;
1359         
1360         if(! get_pconfig($uid,'facebook','no_wall')) {
1361                 $private_wall = intval(get_pconfig($uid,'facebook','private_wall'));
1362                 $s = fetch_url('https://graph.facebook.com/me/feed?access_token=' . $access_token);
1363                 if($s) {
1364                         $j = json_decode($s);
1365                         if (isset($j->data)) {
1366                                 logger('fb_consume_stream: wall: ' . print_r($j,true), LOGGER_DATA);
1367                                 fb_consume_stream($uid,$j,($private_wall) ? false : true);
1368                         } else {
1369                                 logger('fb_consume_stream: wall: got no data from Facebook: ' . print_r($j,true), LOGGER_NORMAL);
1370                         }
1371                 }
1372         }
1373         // Get the last date
1374         $lastdate = get_pconfig($uid,'facebook','lastdate');
1375         // fetch all items since the last date
1376         $j = fb_get_timeline($access_token, $lastdate);
1377         if (isset($j->data)) {
1378                 logger('fb_consume_stream: feed: ' . print_r($j,true), LOGGER_DATA);
1379                 fb_consume_stream($uid,$j,false);
1380
1381                 // Write back the last date
1382                 set_pconfig($uid,'facebook','lastdate', $lastdate);
1383         } else
1384                 logger('fb_consume_stream: feed: got no data from Facebook: ' . print_r($j,true), LOGGER_NORMAL);
1385 }
1386
1387 /**
1388  * @param int $uid
1389  * @param string $link
1390  * @return string
1391  */
1392 function fb_get_photo($uid,$link) {
1393         $access_token = get_pconfig($uid,'facebook','access_token');
1394         if(! $access_token || (! stristr($link,'facebook.com/photo.php')))
1395                 return "";
1396                 //return "\n" . '[url=' . $link . ']' . t('link') . '[/url]';
1397         $ret = preg_match('/fbid=([0-9]*)/',$link,$match);
1398         if($ret)
1399                 $photo_id = $match[1];
1400         else
1401             return "";
1402         $x = fetch_url('https://graph.facebook.com/' . $photo_id . '?access_token=' . $access_token);
1403         $j = json_decode($x);
1404         if($j->picture)
1405                 return "\n\n" . '[url=' . $link . '][img]' . $j->picture . '[/img][/url]';
1406         //else
1407         //      return "\n" . '[url=' . $link . ']' . t('link') . '[/url]';
1408         return "";
1409 }
1410
1411
1412 /**
1413  * @param App $a
1414  * @param array $user
1415  * @param array $self
1416  * @param string $fb_id
1417  * @param bool $wall
1418  * @param array $orig_post
1419  * @param object $cmnt
1420  */
1421 function fb_consume_comment(&$a, &$user, &$self, $fb_id, $wall, &$orig_post, &$cmnt) {
1422
1423     if(! $orig_post)
1424         return;
1425
1426     $top_item = $orig_post['id'];
1427     $uid = IntVal($user[0]['uid']);
1428
1429     $r = q("SELECT * FROM `item` WHERE `uid` = %d AND ( `uri` = '%s' OR `extid` = '%s' ) LIMIT 1",
1430         intval($uid),
1431         dbesc('fb::' . $cmnt->id),
1432         dbesc('fb::' . $cmnt->id)
1433     );
1434     if(count($r))
1435         return;
1436
1437     $cmntdata = array();
1438     $cmntdata['parent'] = $top_item;
1439     $cmntdata['verb'] = ACTIVITY_POST;
1440     $cmntdata['gravity'] = 6;
1441     $cmntdata['uid'] = $uid;
1442     $cmntdata['wall'] = (($wall) ? 1 : 0);
1443     $cmntdata['uri'] = 'fb::' . $cmnt->id;
1444     $cmntdata['parent-uri'] = $orig_post['uri'];
1445     if($cmnt->from->id == $fb_id) {
1446         $cmntdata['contact-id'] = $self[0]['id'];
1447     }
1448     else {
1449         $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d LIMIT 1",
1450             dbesc($cmnt->from->id),
1451             intval($uid)
1452         );
1453         if(count($r)) {
1454             $cmntdata['contact-id'] = $r[0]['id'];
1455             if($r[0]['blocked'] || $r[0]['readonly'])
1456                 return;
1457         }
1458     }
1459     if(! x($cmntdata,'contact-id'))
1460         $cmntdata['contact-id'] = $orig_post['contact-id'];
1461
1462     $cmntdata['app'] = 'facebook';
1463     $cmntdata['created'] = datetime_convert('UTC','UTC',$cmnt->created_time);
1464     $cmntdata['edited']  = datetime_convert('UTC','UTC',$cmnt->created_time);
1465     $cmntdata['verb'] = ACTIVITY_POST;
1466     $cmntdata['author-name'] = $cmnt->from->name;
1467     $cmntdata['author-link'] = 'http://facebook.com/profile.php?id=' . $cmnt->from->id;
1468     $cmntdata['author-avatar'] = 'https://graph.facebook.com/' . $cmnt->from->id . '/picture';
1469     $cmntdata['body'] = $cmnt->message;
1470     $item = item_store($cmntdata);
1471
1472     $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
1473         dbesc($orig_post['uri']),
1474         intval($uid)
1475     );
1476
1477     if(count($myconv)) {
1478         $importer_url = $a->get_baseurl() . '/profile/' . $user[0]['nickname'];
1479
1480         foreach($myconv as $conv) {
1481
1482             // now if we find a match, it means we're in this conversation
1483
1484             if(! link_compare($conv['author-link'],$importer_url))
1485                 continue;
1486
1487             require_once('include/enotify.php');
1488
1489             $conv_parent = $conv['parent'];
1490
1491             notification(array(
1492                 'type'         => NOTIFY_COMMENT,
1493                 'notify_flags' => $user[0]['notify-flags'],
1494                 'language'     => $user[0]['language'],
1495                 'to_name'      => $user[0]['username'],
1496                 'to_email'     => $user[0]['email'],
1497                 'uid'          => $user[0]['uid'],
1498                 'item'         => $cmntdata,
1499                 'link'             => $a->get_baseurl() . '/display/' . $user[0]['nickname'] . '/' . $item,
1500                 'source_name'  => $cmntdata['author-name'],
1501                 'source_link'  => $cmntdata['author-link'],
1502                 'source_photo' => $cmntdata['author-avatar'],
1503                 'verb'         => ACTIVITY_POST,
1504                 'otype'        => 'item',
1505                 'parent'       => $conv_parent,
1506             ));
1507
1508             // only send one notification
1509             break;
1510         }
1511     }
1512 }
1513
1514
1515 /**
1516  * @param App $a
1517  * @param array $user
1518  * @param array $self
1519  * @param string $fb_id
1520  * @param bool $wall
1521  * @param array $orig_post
1522  * @param object $likes
1523  */
1524 function fb_consume_like(&$a, &$user, &$self, $fb_id, $wall, &$orig_post, &$likes) {
1525
1526     $top_item = $orig_post['id'];
1527     $uid = IntVal($user[0]['uid']);
1528
1529     if(! $orig_post)
1530         return;
1531
1532     // If we posted the like locally, it will be found with our url, not the FB url.
1533
1534     $second_url = (($likes->id == $fb_id) ? $self[0]['url'] : 'http://facebook.com/profile.php?id=' . $likes->id);
1535
1536     $r = q("SELECT * FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `verb` = '%s'
1537         AND ( `author-link` = '%s' OR `author-link` = '%s' ) LIMIT 1",
1538         dbesc($orig_post['uri']),
1539         intval($uid),
1540         dbesc(ACTIVITY_LIKE),
1541         dbesc('http://facebook.com/profile.php?id=' . $likes->id),
1542         dbesc($second_url)
1543     );
1544
1545     if(count($r))
1546         return;
1547
1548     $likedata = array();
1549     $likedata['parent'] = $top_item;
1550     $likedata['verb'] = ACTIVITY_LIKE;
1551     $likedata['gravity'] = 3;
1552     $likedata['uid'] = $uid;
1553     $likedata['wall'] = (($wall) ? 1 : 0);
1554     $likedata['uri'] = item_new_uri($a->get_baseurl(), $uid);
1555     $likedata['parent-uri'] = $orig_post['uri'];
1556     if($likes->id == $fb_id)
1557         $likedata['contact-id'] = $self[0]['id'];
1558     else {
1559         $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d AND `blocked` = 0 AND `readonly` = 0 LIMIT 1",
1560             dbesc($likes->id),
1561             intval($uid)
1562         );
1563         if(count($r))
1564             $likedata['contact-id'] = $r[0]['id'];
1565     }
1566     if(! x($likedata,'contact-id'))
1567         $likedata['contact-id'] = $orig_post['contact-id'];
1568
1569     $likedata['app'] = 'facebook';
1570     $likedata['verb'] = ACTIVITY_LIKE;
1571     $likedata['author-name'] = $likes->name;
1572     $likedata['author-link'] = 'http://facebook.com/profile.php?id=' . $likes->id;
1573     $likedata['author-avatar'] = 'https://graph.facebook.com/' . $likes->id . '/picture';
1574
1575     $author  = '[url=' . $likedata['author-link'] . ']' . $likedata['author-name'] . '[/url]';
1576     $objauthor =  '[url=' . $orig_post['author-link'] . ']' . $orig_post['author-name'] . '[/url]';
1577     $post_type = t('status');
1578     $plink = '[url=' . $orig_post['plink'] . ']' . $post_type . '[/url]';
1579     $likedata['object-type'] = ACTIVITY_OBJ_NOTE;
1580
1581     $likedata['body'] = sprintf( t('%1$s likes %2$s\'s %3$s'), $author, $objauthor, $plink);
1582     $likedata['object'] = '<object><type>' . ACTIVITY_OBJ_NOTE . '</type><local>1</local>' .
1583         '<id>' . $orig_post['uri'] . '</id><link>' . xmlify('<link rel="alternate" type="text/html" href="' . xmlify($orig_post['plink']) . '" />') . '</link><title>' . $orig_post['title'] . '</title><content>' . $orig_post['body'] . '</content></object>';
1584
1585     item_store($likedata);
1586 }
1587
1588 /**
1589  * @param App $a
1590  * @param array $user
1591  * @param object $entry
1592  * @param array $self
1593  * @param string $fb_id
1594  * @param bool $wall
1595  * @param array $orig_post
1596  */
1597 function fb_consume_status(&$a, &$user, &$entry, &$self, $fb_id, $wall, &$orig_post) {
1598     $uid = IntVal($user[0]['uid']);
1599     $access_token = get_pconfig($uid, 'facebook', 'access_token');
1600
1601     $s = fetch_url('https://graph.facebook.com/' . $entry->id . '?access_token=' . $access_token);
1602     if($s) {
1603         $j = json_decode($s);
1604         if (isset($j->comments) && isset($j->comments->data))
1605             foreach ($j->comments->data as $cmnt)
1606                 fb_consume_comment($a, $user, $self, $fb_id, $wall, $orig_post, $cmnt);
1607
1608         if (isset($j->likes) && isset($j->likes->data) && isset($j->likes->count)) {
1609             if (count($j->likes->data) == $j->likes->count) {
1610                 foreach ($j->likes->data as $likers) fb_consume_like($a, $user, $self, $fb_id, $wall, $orig_post, $likers);
1611             } else {
1612                 $t = fetch_url('https://graph.facebook.com/' . $entry->id . '/likes?access_token=' . $access_token);
1613                 if ($t) {
1614                     $k = json_decode($t);
1615                     if (isset($k->data))
1616                         foreach ($k->data as $likers)
1617                             fb_consume_like($a, $user, $self, $fb_id, $wall, $orig_post, $likers);
1618                 }
1619             }
1620         }
1621     }
1622 }
1623
1624 /**
1625  * @param int $uid
1626  * @param object $j
1627  * @param bool $wall
1628  */
1629 function fb_consume_stream($uid,$j,$wall = false) {
1630
1631         $a = get_app();
1632
1633         $user = q("SELECT * FROM `user` WHERE `uid` = %d AND `account_expired` = 0 LIMIT 1",
1634                 intval($uid)
1635         );
1636         if(! count($user))
1637                 return;
1638
1639         // $my_local_url = $a->get_baseurl() . '/profile/' . $user[0]['nickname'];
1640
1641         $no_linking = get_pconfig($uid,'facebook','no_linking');
1642         if($no_linking)
1643                 return;
1644
1645         $self = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
1646                 intval($uid)
1647         );
1648
1649         $blocked_apps = get_pconfig($uid,'facebook','blocked_apps');
1650         $blocked_apps_arr = explode(',',$blocked_apps);
1651
1652         $sync_comments = get_config('facebook', 'sync_comments');
1653
1654     /** @var string $self_id  */
1655         $self_id = get_pconfig($uid,'facebook','self_id');
1656         if(! count($j->data) || (! strlen($self_id)))
1657                 return;
1658
1659     $top_item = 0;
1660
1661     foreach($j->data as $entry) {
1662                 logger('fb_consume: entry: ' . print_r($entry,true), LOGGER_DATA);
1663                 $datarray = array();
1664
1665                 $r = q("SELECT * FROM `item` WHERE ( `uri` = '%s' OR `extid` = '%s') AND `uid` = %d LIMIT 1",
1666                                 dbesc('fb::' . $entry->id),
1667                                 dbesc('fb::' . $entry->id),
1668                                 intval($uid)
1669                 );
1670                 if(count($r)) {
1671                         $orig_post = $r[0];
1672                         $top_item = $r[0]['id'];
1673                 }
1674                 else {
1675                         $orig_post = null;
1676                 }
1677
1678                 if(! $orig_post) {
1679                         $datarray['gravity'] = 0;
1680                         $datarray['uid'] = $uid;
1681                         $datarray['wall'] = (($wall) ? 1 : 0);
1682                         $datarray['uri'] = $datarray['parent-uri'] = 'fb::' . $entry->id;
1683                         $from = $entry->from;
1684                         if($from->id == $self_id)
1685                                 $datarray['contact-id'] = $self[0]['id'];
1686                         else {
1687                                 // Looking if user is known - if not he is added
1688                                 $access_token = get_pconfig($uid, 'facebook', 'access_token');
1689                                 fb_get_friends_sync_new($uid, $access_token, array($from));
1690
1691                                 $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d AND `blocked` = 0 AND `readonly` = 0 LIMIT 1",
1692                                         dbesc($from->id),
1693                                         intval($uid)
1694                                 );
1695                                 if(count($r))
1696                                         $datarray['contact-id'] = $r[0]['id'];
1697                         }
1698
1699                         // don't store post if we don't have a contact
1700                         if(! x($datarray,'contact-id')) {
1701                                 logger('facebook: no contact '.$from->name.' '.$from->id.'. post ignored');
1702                                 continue;
1703                         }
1704
1705                         $datarray['verb'] = ACTIVITY_POST;
1706                         if($wall) {
1707                                 $datarray['owner-name'] = $self[0]['name'];
1708                                 $datarray['owner-link'] = $self[0]['url'];
1709                                 $datarray['owner-avatar'] = $self[0]['thumb'];
1710                         }
1711                         if(isset($entry->application) && isset($entry->application->name) && strlen($entry->application->name))
1712                                 $datarray['app'] = strip_tags($entry->application->name);
1713                         else
1714                                 $datarray['app'] = 'facebook';
1715
1716                         $found_blocked = false;
1717
1718                         if(count($blocked_apps_arr)) {
1719                                 foreach($blocked_apps_arr as $bad_appl) {
1720                                         if(strlen(trim($bad_appl)) && (stristr($datarray['app'],trim($bad_appl)))) {
1721                                                 $found_blocked = true;
1722                                         }
1723                                 }
1724                         }
1725                                 
1726                         if($found_blocked) {
1727                                 logger('facebook: blocking application: ' . $datarray['app']);
1728                                 continue;
1729                         }
1730
1731                         $datarray['author-name'] = $from->name;
1732                         $datarray['author-link'] = 'http://facebook.com/profile.php?id=' . $from->id;
1733                         $datarray['author-avatar'] = 'https://graph.facebook.com/' . $from->id . '/picture';
1734                         $datarray['plink'] = $datarray['author-link'] . '&v=wall&story_fbid=' . substr($entry->id,strpos($entry->id,'_') + 1);
1735
1736                         logger('facebook: post '.$entry->id.' from '.$from->name);
1737
1738                         $datarray['body'] = (isset($entry->message) ? escape_tags($entry->message) : '');
1739
1740                         if(isset($entry->name) and isset($entry->link))
1741                                 $datarray['body'] .= "\n\n[bookmark=".$entry->link."]".$entry->name."[/bookmark]";
1742                         elseif (isset($entry->name))
1743                                 $datarray['body'] .= "\n\n[b]" . $entry->name."[/b]";
1744
1745                         if(isset($entry->caption)) {
1746                                 if(!isset($entry->name) and isset($entry->link))
1747                                         $datarray['body'] .= "\n\n[bookmark=".$entry->link."]".$entry->caption."[/bookmark]";
1748                                 else
1749                                         $datarray['body'] .= "[i]" . $entry->caption."[/i]\n";
1750                         }
1751
1752                         if(!isset($entry->caption) and !isset($entry->name)) {
1753                                 if (isset($entry->link))
1754                                         $datarray['body'] .= "\n[url]".$entry->link."[/url]\n";
1755                                 else
1756                                         $datarray['body'] .= "\n";
1757                         }
1758
1759                         $quote = "";
1760                         if(isset($entry->description))
1761                                 $quote = $entry->description;
1762
1763                         if (isset($entry->properties))
1764                                 foreach ($entry->properties as $property)
1765                                         $quote .= "\n".$property->name.": [url=".$property->href."]".$property->text."[/url]";
1766
1767                         if ($quote)
1768                                 $datarray['body'] .= "\n[quote]".$quote."[/quote]";
1769
1770                         // Only import the picture when the message is no video
1771                         // oembed display a picture of the video as well 
1772                         if ($entry->type != "video") {
1773                                 if(isset($entry->picture) && isset($entry->link)) {
1774                                         $datarray['body'] .= "\n" . '[url=' . $entry->link . '][img]'.$entry->picture.'[/img][/url]';   
1775                                 }
1776                                 else {
1777                                         if(isset($entry->picture))
1778                                                 $datarray['body'] .= "\n" . '[img]' . $entry->picture . '[/img]';
1779                                         // if just a link, it may be a wall photo - check
1780                                         if(isset($entry->link))
1781                                                 $datarray['body'] .= fb_get_photo($uid,$entry->link);
1782                                 }
1783                         }
1784
1785                         if (($datarray['app'] == "Events") and isset($entry->actions))
1786                                 foreach ($entry->actions as $action)
1787                                         if ($action->name == "View")
1788                                                 $datarray['body'] .= " [url=".$action->link."]".$entry->story."[/url]";
1789
1790                         // Just as a test - to see if these are the missing entries
1791                         //if(trim($datarray['body']) == '')
1792                         //      $datarray['body'] = $entry->story;
1793
1794                         // Adding the "story" text to see if there are useful data in it (testing)
1795                         //if (($datarray['app'] != "Events") and $entry->story)
1796                         //      $datarray['body'] .= "\n".$entry->story;
1797
1798                         if(trim($datarray['body']) == '') {
1799                                 logger('facebook: empty body '.$entry->id.' '.print_r($entry, true));
1800                                 continue;
1801                         }
1802
1803                         $datarray['body'] .= "\n";
1804
1805                         if (isset($entry->icon))
1806                                 $datarray['body'] .= "[img]".$entry->icon."[/img] &nbsp; ";
1807
1808                         if (isset($entry->actions))
1809                                 foreach ($entry->actions as $action)
1810                                         if (($action->name != "Comment") and ($action->name != "Like"))
1811                                                 $datarray['body'] .= "[url=".$action->link."]".$action->name."[/url] &nbsp; ";
1812
1813                         $datarray['body'] = trim($datarray['body']);
1814
1815                         //if(($datarray['body'] != '') and ($uid == 1))
1816                         //      $datarray['body'] .= "[noparse]".print_r($entry, true)."[/noparse]";
1817
1818             if (isset($entry->place)) {
1819                             if ($entry->place->name or $entry->place->location->street or
1820                                     $entry->place->location->city or $entry->place->location->Denmark) {
1821                                     $datarray['coord'] = '';
1822                                     if ($entry->place->name)
1823                                             $datarray['coord'] .= $entry->place->name;
1824                                     if ($entry->place->location->street)
1825                                             $datarray['coord'] .= $entry->place->location->street;
1826                                     if ($entry->place->location->city)
1827                                             $datarray['coord'] .= " ".$entry->place->location->city;
1828                                     if ($entry->place->location->country)
1829                                             $datarray['coord'] .= " ".$entry->place->location->country;
1830                             } else if ($entry->place->location->latitude and $entry->place->location->longitude)
1831                                     $datarray['coord'] = substr($entry->place->location->latitude, 0, 8)
1832                                                         .' '.substr($entry->place->location->longitude, 0, 8);
1833             }
1834                         $datarray['created'] = datetime_convert('UTC','UTC',$entry->created_time);
1835                         $datarray['edited'] = datetime_convert('UTC','UTC',$entry->updated_time);
1836
1837                         // If the entry has a privacy policy, we cannot assume who can or cannot see it,
1838                         // as the identities are from a foreign system. Mark it as private to the owner.
1839
1840                         if(isset($entry->privacy) && $entry->privacy->value !== 'EVERYONE') {
1841                                 $datarray['private'] = 1;
1842                                 $datarray['allow_cid'] = '<' . $self[0]['id'] . '>';
1843                         }
1844
1845                         $top_item = item_store($datarray);
1846                         $r = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1847                                 intval($top_item),
1848                                 intval($uid)
1849                         );
1850                         if(count($r)) {
1851                                 $orig_post = $r[0];
1852                                 logger('fb: new top level item posted');
1853                         }
1854                 }
1855
1856                 /**  @var array $orig_post */
1857
1858         $likers_num = (isset($entry->likes) && isset($entry->likes->count) ? IntVal($entry->likes->count) : 0 );
1859                 if(isset($entry->likes) && isset($entry->likes->data))
1860                         $likers = $entry->likes->data;
1861                 else
1862                         $likers = null;
1863
1864         $comments_num = (isset($entry->comments) && isset($entry->comments->count) ? IntVal($entry->comments->count) : 0 );
1865                 if(isset($entry->comments) && isset($entry->comments->data))
1866                         $comments = $entry->comments->data;
1867                 else
1868                         $comments = null;
1869
1870         $needs_sync = false;
1871
1872         if(is_array($likers)) {
1873                         foreach($likers as $likes) fb_consume_like($a, $user, $self, $self_id, $wall, $orig_post, $likes);
1874             if ($sync_comments) {
1875                 $r = q("SELECT COUNT(*) likes FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `verb` = '%s' AND `parent-uri` != `uri`",
1876                     dbesc($orig_post['uri']),
1877                     intval($uid),
1878                     dbesc(ACTIVITY_LIKE)
1879                 );
1880                 if ($r[0]['likes'] < $likers_num) {
1881                     logger('fb_consume_stream: missing likes found for ' . $orig_post['uri'] . ' (we have ' . $r[0]['likes'] . ' of ' . $likers_num . '). Synchronizing...', LOGGER_DEBUG);
1882                     $needs_sync = true;
1883                 }
1884             }
1885                 }
1886
1887                 if(is_array($comments)) {
1888                         foreach($comments as $cmnt) fb_consume_comment($a, $user, $self, $self_id, $wall, $orig_post, $cmnt);
1889                         if ($sync_comments) {
1890                             $r = q("SELECT COUNT(*) comments FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `verb` = '%s' AND `parent-uri` != `uri`",
1891                     dbesc($orig_post['uri']),
1892                     intval($uid),
1893                     ACTIVITY_POST
1894                 );
1895                             if ($r[0]['comments'] < $comments_num) {
1896                     logger('fb_consume_stream: missing comments found for ' . $orig_post['uri'] . ' (we have ' . $r[0]['comments'] . ' of ' . $comments_num . '). Synchronizing...', LOGGER_DEBUG);
1897                     $needs_sync = true;
1898                 }
1899                         }
1900                 }
1901
1902                 if ($needs_sync) fb_consume_status($a, $user, $entry, $self, $self_id, $wall, $orig_post);
1903         }
1904 }
1905
1906
1907 /**
1908  * @return bool|string
1909  */
1910 function fb_get_app_access_token() {
1911         
1912         $acc_token = get_config('facebook','app_access_token');
1913         
1914         if ($acc_token !== false) return $acc_token;
1915         
1916         $appid = get_config('facebook','appid');
1917         $appsecret = get_config('facebook', 'appsecret');
1918         
1919         if ($appid === false || $appsecret === false) {
1920                 logger('fb_get_app_access_token: appid and/or appsecret not set', LOGGER_DEBUG);
1921                 return false;
1922         }
1923         logger('https://graph.facebook.com/oauth/access_token?client_id=' . $appid . '&client_secret=' . $appsecret . '&grant_type=client_credentials', LOGGER_DATA);
1924         $x = fetch_url('https://graph.facebook.com/oauth/access_token?client_id=' . $appid . '&client_secret=' . $appsecret . '&grant_type=client_credentials');
1925         
1926         if(strpos($x,'access_token=') !== false) {
1927                 logger('fb_get_app_access_token: returned access token: ' . $x, LOGGER_DATA);
1928         
1929                 $token = str_replace('access_token=', '', $x);
1930                 if(strpos($token,'&') !== false)
1931                         $token = substr($token,0,strpos($token,'&'));
1932                 
1933                 if ($token == "") {
1934                         logger('fb_get_app_access_token: empty token: ' . $x, LOGGER_DEBUG);
1935                         return false;
1936                 }
1937                 set_config('facebook','app_access_token',$token);
1938                 return $token;
1939         } else {
1940                 logger('fb_get_app_access_token: response did not contain an access_token: ' . $x, LOGGER_DATA);
1941                 return false;
1942         }
1943 }
1944
1945 function facebook_subscription_del_users() {
1946         $a = get_app();
1947         $access_token = fb_get_app_access_token();
1948         
1949         $url = "https://graph.facebook.com/" . get_config('facebook', 'appid'  ) . "/subscriptions?access_token=" . $access_token;
1950         facebook_delete_url($url);
1951         
1952         if (!facebook_check_realtime_active()) del_config('facebook', 'realtime_active');
1953 }
1954
1955 /**
1956  * @param bool $second_try
1957  */
1958 function facebook_subscription_add_users($second_try = false) {
1959         $a = get_app();
1960         $access_token = fb_get_app_access_token();
1961         
1962         $url = "https://graph.facebook.com/" . get_config('facebook', 'appid'  ) . "/subscriptions?access_token=" . $access_token;
1963         
1964         list($usec, $sec) = explode(" ", microtime());
1965         $verify_token = sha1($usec . $sec . rand(0, 999999999));
1966         set_config('facebook', 'cb_verify_token', $verify_token);
1967         
1968         $cb = $a->get_baseurl() . '/facebook/?realtime_cb=1';
1969         
1970         $j = post_url($url,array(
1971                 "object" => "user",
1972                 "fields" => "feed,friends",
1973                 "callback_url" => $cb,
1974                 "verify_token" => $verify_token,
1975         ));
1976         del_config('facebook', 'cb_verify_token');
1977         
1978         if ($j) {
1979                 $x = json_decode($j);
1980                 logger("Facebook reponse: " . $j, LOGGER_DATA);
1981                 if (isset($x->error)) {
1982                         logger('facebook_subscription_add_users: got an error: ' . $j);
1983                         if ($x->error->type == "OAuthException" && $x->error->code == 190) {
1984                                 del_config('facebook', 'app_access_token');
1985                                 if ($second_try === false) facebook_subscription_add_users(true);
1986                         }
1987                 } else {
1988                         logger('facebook_subscription_add_users: sucessful');
1989                         if (facebook_check_realtime_active()) set_config('facebook', 'realtime_active', 1);
1990                 }
1991         };
1992 }
1993
1994 /**
1995  * @return null|array
1996  */
1997 function facebook_subscriptions_get() {
1998         
1999         $access_token = fb_get_app_access_token();
2000         if (!$access_token) return null;
2001         
2002         $url = "https://graph.facebook.com/" . get_config('facebook', 'appid'  ) . "/subscriptions?access_token=" . $access_token;
2003         $j = fetch_url($url);
2004         $ret = null;
2005         if ($j) {
2006                 $x = json_decode($j);
2007                 if (isset($x->data)) $ret = $x->data;
2008         }
2009         return $ret;
2010 }
2011
2012
2013 /**
2014  * @return bool
2015  */
2016 function facebook_check_realtime_active() {
2017         $ret = facebook_subscriptions_get();
2018         if (is_null($ret)) return false;
2019         if (is_array($ret)) foreach ($ret as $re) if (is_object($re) && $re->object == "user") return true;
2020         return false;
2021 }
2022
2023
2024
2025
2026 // DELETE-request to $url
2027
2028 if(! function_exists('facebook_delete_url')) {
2029     /**
2030      * @param string $url
2031      * @param null|array $headers
2032      * @param int $redirects
2033      * @param int $timeout
2034      * @return bool|string
2035      */
2036     function facebook_delete_url($url,$headers = null, &$redirects = 0, $timeout = 0) {
2037         $a = get_app();
2038         $ch = curl_init($url);
2039         if(($redirects > 8) || (! $ch)) 
2040                 return false;
2041
2042         curl_setopt($ch, CURLOPT_HEADER, true);
2043         curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
2044         curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
2045         curl_setopt($ch, CURLOPT_USERAGENT, "Friendica");
2046
2047         if(intval($timeout)) {
2048                 curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
2049         }
2050         else {
2051                 $curl_time = intval(get_config('system','curl_timeout'));
2052                 curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
2053         }
2054
2055         if(defined('LIGHTTPD')) {
2056                 if(!is_array($headers)) {
2057                         $headers = array('Expect:');
2058                 } else {
2059                         if(!in_array('Expect:', $headers)) {
2060                                 array_push($headers, 'Expect:');
2061                         }
2062                 }
2063         }
2064         if($headers)
2065                 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
2066
2067         $check_cert = get_config('system','verifyssl');
2068         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
2069         $prx = get_config('system','proxy');
2070         if(strlen($prx)) {
2071                 curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
2072                 curl_setopt($ch, CURLOPT_PROXY, $prx);
2073                 $prxusr = get_config('system','proxyuser');
2074                 if(strlen($prxusr))
2075                         curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
2076         }
2077
2078         $a->set_curl_code(0);
2079
2080         // don't let curl abort the entire application
2081         // if it throws any errors.
2082
2083         $s = @curl_exec($ch);
2084
2085         $base = $s;
2086         $curl_info = curl_getinfo($ch);
2087         $http_code = $curl_info['http_code'];
2088
2089         $header = '';
2090
2091         // Pull out multiple headers, e.g. proxy and continuation headers
2092         // allow for HTTP/2.x without fixing code
2093
2094         while(preg_match('/^HTTP\/[1-2].+? [1-5][0-9][0-9]/',$base)) {
2095                 $chunk = substr($base,0,strpos($base,"\r\n\r\n")+4);
2096                 $header .= $chunk;
2097                 $base = substr($base,strlen($chunk));
2098         }
2099
2100         if($http_code == 301 || $http_code == 302 || $http_code == 303) {
2101         $matches = array();
2102         preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
2103         $url = trim(array_pop($matches));
2104         $url_parsed = @parse_url($url);
2105         if (isset($url_parsed)) {
2106             $redirects++;
2107             return facebook_delete_url($url,$headers,$redirects,$timeout);
2108         }
2109     }
2110         $a->set_curl_code($http_code);
2111         $body = substr($s,strlen($header));
2112
2113         $a->set_curl_headers($header);
2114
2115         curl_close($ch);
2116         return($body);
2117 }}