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