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