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