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