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