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