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