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