]> git.mxchange.org Git - friendica-addons.git/blob - facebook/facebook.php
Handling of empty messages improved. They weren't posted.
[friendica-addons.git] / facebook / facebook.php
1 <?php
2 /**
3  * Name: Facebook Connector
4  * Version: 1.1
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  * 1. register an API key for your site from developer.facebook.com
13  *   a. We'd be very happy if you include "Friendica" in the application name
14  *      to increase name recognition. The Friendica icons are also present
15  *      in the images directory and may be uploaded as a Facebook app icon.
16  *      Use images/friendica-16.jpg for the Icon and images/friendica-128.jpg for the Logo.
17  *   b. The url should be your site URL with a trailing slash.
18  *      Friendica is a software application and does not require a Privacy Policy 
19  *      or Terms of Service, though your installation of it might. Facebook may require
20  *      that you provide a Privacy Policy, which we find ironic.  
21  *   c. Set the following values in your .htconfig.php file
22  *         $a->config['facebook']['appid'] = 'xxxxxxxxxxx';
23  *         $a->config['facebook']['appsecret'] = 'xxxxxxxxxxxxxxx';
24  *      Replace with the settings Facebook gives you.
25  *   d. Navigate to Set Web->Site URL & Domain -> Website Settings.  Set 
26  *      Site URL to yoursubdomain.yourdomain.com. Set Site Domain to your 
27  *      yourdomain.com.
28  * 2. (This step is now obsolete. Enable the plugin via the Admin panel.)
29  *     Enable the facebook plugin by including it in .htconfig.php - e.g. 
30  *     $a->config['system']['addon'] = 'plugin1,plugin2,facebook';
31  * 3. Visit the Facebook Settings section of the "Settings->Plugin Settings" page.
32  *    and click 'Install Facebook Connector'.
33  * 4. This will ask you to login to Facebook and grant permission to the 
34  *    plugin to do its stuff. Allow it to do so. 
35  * 5. Optional step: If you want to use Facebook Real Time Updates (so new messages
36  *    and new contacts are added ~1min after they are postet / added on FB), go to
37  *    Settings -> plugins -> facebook and press the "Activate Real-Time Updates"-button.
38  * 6. You're done. To turn it off visit the Plugin Settings page again and
39  *    'Remove Facebook posting'.
40  *
41  * Vidoes and embeds will not be posted if there is no other content. Links 
42  * and images will be converted to a format suitable for the Facebook API and 
43  * long posts truncated - with a link to view the full post. 
44  *
45  * Facebook contacts will not be able to view private photos, as they are not able to
46  * authenticate to your site to establish identity. We will address this 
47  * in a future release.
48  */
49  
50  /** TODO
51  * - Implement a method for the administrator to delete all configuration data the plugin has created,
52  *   e.g. the app_access_token
53  * - Implement a configuration option to set the polling interval system-wide
54  */
55
56 // Size of maximum post length increased
57 // see http://www.facebook.com/schrep/posts/203969696349811
58 // define('FACEBOOK_MAXPOSTLEN', 420);
59 define('FACEBOOK_MAXPOSTLEN', 63206);
60
61
62 function facebook_install() {
63         register_hook('post_local',       'addon/facebook/facebook.php', 'facebook_post_local');
64         register_hook('notifier_normal',  'addon/facebook/facebook.php', 'facebook_post_hook');
65         register_hook('jot_networks',     'addon/facebook/facebook.php', 'facebook_jot_nets');
66         register_hook('connector_settings',  'addon/facebook/facebook.php', 'facebook_plugin_settings');
67         register_hook('cron',             'addon/facebook/facebook.php', 'facebook_cron');
68         register_hook('enotify',          'addon/facebook/facebook.php', 'facebook_enotify');
69         register_hook('queue_predeliver', 'addon/facebook/facebook.php', 'fb_queue_hook');
70 }
71
72
73 function facebook_uninstall() {
74         unregister_hook('post_local',       'addon/facebook/facebook.php', 'facebook_post_local');
75         unregister_hook('notifier_normal',  'addon/facebook/facebook.php', 'facebook_post_hook');
76         unregister_hook('jot_networks',     'addon/facebook/facebook.php', 'facebook_jot_nets');
77         unregister_hook('connector_settings',  'addon/facebook/facebook.php', 'facebook_plugin_settings');
78         unregister_hook('cron',             'addon/facebook/facebook.php', 'facebook_cron');
79         unregister_hook('enotify',          'addon/facebook/facebook.php', 'facebook_enotify');
80         unregister_hook('queue_predeliver', 'addon/facebook/facebook.php', 'fb_queue_hook');
81
82         // hook moved
83         unregister_hook('post_local_end',  'addon/facebook/facebook.php', 'facebook_post_hook');
84         unregister_hook('plugin_settings',  'addon/facebook/facebook.php', 'facebook_plugin_settings');
85 }
86
87
88 /* declare the facebook_module function so that /facebook url requests will land here */
89
90 function facebook_module() {}
91
92
93
94 // If a->argv[1] is a nickname, this is a callback from Facebook oauth requests.
95 // If $_REQUEST["realtime_cb"] is set, this is a callback from the Real-Time Updates API
96
97 function facebook_init(&$a) {
98         
99         if (x($_REQUEST, "realtime_cb") && x($_REQUEST, "realtime_cb")) {
100                 logger("facebook_init: Facebook Real-Time callback called", LOGGER_DEBUG);
101                 
102                 if (x($_REQUEST, "hub_verify_token")) {
103                         // this is the verification callback while registering for real time updates
104                         
105                         $verify_token = get_config('facebook', 'cb_verify_token');
106                         if ($verify_token != $_REQUEST["hub_verify_token"]) {
107                                 logger('facebook_init: Wrong Facebook Callback Verifier - expected ' . $verify_token . ', got ' . $_REQUEST["hub_verify_token"]);
108                                 return;
109                         }
110                         
111                         if (x($_REQUEST, "hub_challenge")) {
112                                 logger('facebook_init: Answering Challenge: ' . $_REQUEST["hub_challenge"], LOGGER_DATA);
113                                 echo $_REQUEST["hub_challenge"];
114                                 die();
115                         }
116                 }
117                 
118                 require_once('include/items.php');
119                 
120                 // this is a status update
121                 $content = file_get_contents("php://input");
122                 if (is_numeric($content)) $content = file_get_contents("php://input");
123                 $js = json_decode($content);
124                 logger(print_r($js, true), LOGGER_DATA);
125                 
126                 if (!isset($js->object) || $js->object != "user" || !isset($js->entry)) {
127                         logger('facebook_init: Could not parse Real-Time Update data', LOGGER_DEBUG);
128                         return;
129                 }
130                 
131                 $affected_users = array("feed" => array(), "friends" => array());
132                 
133                 foreach ($js->entry as $entry) {
134                         $fbuser = $entry->uid;
135                         foreach ($entry->changed_fields as $field) {
136                                 if (!isset($affected_users[$field])) {
137                                         logger('facebook_init: Unknown field "' . $field . '"');
138                                         continue;
139                                 }
140                                 if (in_array($fbuser, $affected_users[$field])) continue;
141                                 
142                                 $r = q("SELECT `uid` FROM `pconfig` WHERE `cat` = 'facebook' AND `k` = 'self_id' AND `v` = '%s' LIMIT 1", dbesc($fbuser));
143                                 if(! count($r))
144                                         continue;
145                                 $uid = $r[0]['uid'];
146                                 
147                                 $access_token = get_pconfig($uid,'facebook','access_token');
148                                 if(! $access_token)
149                                         return;
150                                 
151                                 switch ($field) {
152                                         case "feed":
153                                                 logger('facebook_init: FB-User ' . $fbuser . ' / feed', LOGGER_DEBUG);
154                                                 
155                                                 if(! get_pconfig($uid,'facebook','no_wall')) {
156                                                         $private_wall = intval(get_pconfig($uid,'facebook','private_wall'));
157                                                         $s = fetch_url('https://graph.facebook.com/me/feed?access_token=' . $access_token);
158                                                         if($s) {
159                                                                 $j = json_decode($s);
160                                                                 if (isset($j->data)) {
161                                                                         logger('facebook_init: wall: ' . print_r($j,true), LOGGER_DATA);
162                                                                         fb_consume_stream($uid,$j,($private_wall) ? false : true);
163                                                                 } else {
164                                                                         logger('facebook_init: wall: got no data from Facebook: ' . print_r($j,true), LOGGER_NORMAL);
165                                                                 }
166                                                         }
167                                                 }
168                                                 
169                                         break;
170                                         case "friends":
171                                                 logger('facebook_init: FB-User ' . $fbuser . ' / friends', LOGGER_DEBUG);
172                                                 
173                                                 fb_get_friends($uid, false);
174                                                 set_pconfig($uid,'facebook','friend_check',time());
175                                         break;
176                                         default:
177                                                 logger('facebook_init: Unknown callback field for ' . $fbuser, LOGGER_NORMAL);
178                                 }
179                                 $affected_users[$field][] = $fbuser;
180                         }
181                 }
182         }
183
184         
185         if($a->argc != 2)
186                 return;
187         $nick = $a->argv[1];
188         if(strlen($nick))
189                 $r = q("SELECT `uid` FROM `user` WHERE `nickname` = '%s' LIMIT 1",
190                                 dbesc($nick)
191                 );
192         if(! count($r))
193                 return;
194
195         $uid           = $r[0]['uid'];
196         $auth_code     = (x($_GET, 'code') ? $_GET['code'] : '');
197         $error         = (x($_GET, 'error_description') ? $_GET['error_description'] : '');
198
199
200         if($error)
201                 logger('facebook_init: Error: ' . $error);
202
203         if($auth_code && $uid) {
204
205                 $appid = get_config('facebook','appid');
206                 $appsecret = get_config('facebook', 'appsecret');
207
208                 $x = fetch_url('https://graph.facebook.com/oauth/access_token?client_id='
209                         . $appid . '&client_secret=' . $appsecret . '&redirect_uri='
210                         . urlencode($a->get_baseurl() . '/facebook/' . $nick) 
211                         . '&code=' . $auth_code);
212
213                 logger('facebook_init: returned access token: ' . $x, LOGGER_DATA);
214
215                 if(strpos($x,'access_token=') !== false) {
216                         $token = str_replace('access_token=', '', $x);
217                         if(strpos($token,'&') !== false)
218                                 $token = substr($token,0,strpos($token,'&'));
219                         set_pconfig($uid,'facebook','access_token',$token);
220                         set_pconfig($uid,'facebook','post','1');
221                         if(get_pconfig($uid,'facebook','no_linking') === false)
222                                 set_pconfig($uid,'facebook','no_linking',1);
223                         fb_get_self($uid);
224                         fb_get_friends($uid, true);
225                         fb_consume_all($uid);
226
227                 }
228
229         }
230
231 }
232
233
234 function fb_get_self($uid) {
235         $access_token = get_pconfig($uid,'facebook','access_token');
236         if(! $access_token)
237                 return;
238         $s = fetch_url('https://graph.facebook.com/me/?access_token=' . $access_token);
239         if($s) {
240                 $j = json_decode($s);
241                 set_pconfig($uid,'facebook','self_id',(string) $j->id);
242         }
243 }
244
245 function fb_get_friends_sync_new($uid, $access_token, $person) {
246         $link = 'http://facebook.com/profile.php?id=' . $person->id;
247         
248         $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `url` = '%s' LIMIT 1",
249                 intval($uid),
250                 dbesc($link)
251         );
252         
253         if (count($r) == 0) {
254                 logger('fb_get_friends: new contact found: ' . $link, LOGGER_DEBUG);
255                 
256                 fb_get_friends_sync_full($uid, $access_token, $person);
257         }
258 }
259
260 function fb_get_friends_sync_full($uid, $access_token, $person) {
261         $s = fetch_url('https://graph.facebook.com/' . $person->id . '?access_token=' . $access_token);
262         if($s) {
263                 $jp = json_decode($s);
264                 logger('fb_get_friends: info: ' . print_r($jp,true), LOGGER_DATA);
265
266                 // always use numeric link for consistency
267
268                 $jp->link = 'http://facebook.com/profile.php?id=' . $person->id;
269
270                 // check if we already have a contact
271
272                 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `url` = '%s' LIMIT 1",
273                         intval($uid),
274                         dbesc($jp->link)
275                 );                      
276
277                 if(count($r)) {
278
279                         // check that we have all the photos, this has been known to fail on occasion
280
281                         if((! $r[0]['photo']) || (! $r[0]['thumb']) || (! $r[0]['micro'])) {  
282                                 require_once("Photo.php");
283
284                                 $photos = import_profile_photo('https://graph.facebook.com/' . $jp->id . '/picture', $uid, $r[0]['id']);
285
286                                 $r = q("UPDATE `contact` SET `photo` = '%s', 
287                                         `thumb` = '%s',
288                                         `micro` = '%s', 
289                                         `name-date` = '%s', 
290                                         `uri-date` = '%s', 
291                                         `avatar-date` = '%s'
292                                         WHERE `id` = %d LIMIT 1
293                                 ",
294                                         dbesc($photos[0]),
295                                         dbesc($photos[1]),
296                                         dbesc($photos[2]),
297                                         dbesc(datetime_convert()),
298                                         dbesc(datetime_convert()),
299                                         dbesc(datetime_convert()),
300                                         intval($r[0]['id'])
301                                 );                      
302                         }       
303                         return;
304                 }
305                 else {
306
307                         // create contact record 
308                         $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `addr`, `alias`, `notify`, `poll`, 
309                                 `name`, `nick`, `photo`, `network`, `rel`, `priority`,
310                                 `writable`, `blocked`, `readonly`, `pending` )
311                                 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, 0, 0, 0 ) ",
312                                 intval($uid),
313                                 dbesc(datetime_convert()),
314                                 dbesc($jp->link),
315                                 dbesc(normalise_link($jp->link)),
316                                 dbesc(''),
317                                 dbesc(''),
318                                 dbesc($jp->id),
319                                 dbesc('facebook ' . $jp->id),
320                                 dbesc($jp->name),
321                                 dbesc(($jp->nickname) ? $jp->nickname : strtolower($jp->first_name)),
322                                 dbesc('https://graph.facebook.com/' . $jp->id . '/picture'),
323                                 dbesc(NETWORK_FACEBOOK),
324                                 intval(CONTACT_IS_FRIEND),
325                                 intval(1),
326                                 intval(1)
327                         );
328                 }
329
330                 $r = q("SELECT * FROM `contact` WHERE `url` = '%s' AND `uid` = %d LIMIT 1",
331                         dbesc($jp->link),
332                         intval($uid)
333                 );
334
335                 if(! count($r)) {
336                         return;
337                 }
338
339                 $contact = $r[0];
340                 $contact_id  = $r[0]['id'];
341
342                 require_once("Photo.php");
343
344                 $photos = import_profile_photo($r[0]['photo'],$uid,$contact_id);
345
346                 $r = q("UPDATE `contact` SET `photo` = '%s', 
347                         `thumb` = '%s',
348                         `micro` = '%s', 
349                         `name-date` = '%s', 
350                         `uri-date` = '%s', 
351                         `avatar-date` = '%s'
352                         WHERE `id` = %d LIMIT 1
353                 ",
354                         dbesc($photos[0]),
355                         dbesc($photos[1]),
356                         dbesc($photos[2]),
357                         dbesc(datetime_convert()),
358                         dbesc(datetime_convert()),
359                         dbesc(datetime_convert()),
360                         intval($contact_id)
361                 );                      
362
363         }
364 }
365
366 // if $fullsync is true, only new contacts are searched for
367
368 function fb_get_friends($uid, $fullsync = true) {
369
370         $r = q("SELECT `uid` FROM `user` WHERE `uid` = %d AND `account_expired` = 0 LIMIT 1",
371                 intval($uid)
372         );
373         if(! count($r))
374                 return;
375
376         $access_token = get_pconfig($uid,'facebook','access_token');
377
378         $no_linking = get_pconfig($uid,'facebook','no_linking');
379         if($no_linking)
380                 return;
381
382         if(! $access_token)
383                 return;
384         $s = fetch_url('https://graph.facebook.com/me/friends?access_token=' . $access_token);
385         if($s) {
386                 logger('facebook: fb_get_friends: ' . $s, LOGGER_DATA);
387                 $j = json_decode($s);
388                 logger('facebook: fb_get_friends: json: ' . print_r($j,true), LOGGER_DATA);
389                 if(! $j->data)
390                         return;
391                 foreach($j->data as $person)
392                         if ($fullsync)
393                                 fb_get_friends_sync_full($uid, $access_token, $person);
394                         else
395                                 fb_get_friends_sync_new($uid, $access_token, $person);
396         }
397 }
398
399 // This is the POST method to the facebook settings page
400 // Content is posted to Facebook in the function facebook_post_hook() 
401
402 function facebook_post(&$a) {
403
404         $uid = local_user();
405         if($uid){
406
407                 $value = ((x($_POST,'post_by_default')) ? intval($_POST['post_by_default']) : 0);
408                 set_pconfig($uid,'facebook','post_by_default', $value);
409
410                 $no_linking = get_pconfig($uid,'facebook','no_linking');
411
412                 $no_wall = ((x($_POST,'facebook_no_wall')) ? intval($_POST['facebook_no_wall']) : 0);
413                 set_pconfig($uid,'facebook','no_wall',$no_wall);
414
415                 $private_wall = ((x($_POST,'facebook_private_wall')) ? intval($_POST['facebook_private_wall']) : 0);
416                 set_pconfig($uid,'facebook','private_wall',$private_wall);
417         
418
419                 set_pconfig($uid,'facebook','blocked_apps',escape_tags(trim($_POST['blocked_apps'])));
420
421                 $linkvalue = ((x($_POST,'facebook_linking')) ? intval($_POST['facebook_linking']) : 0);
422                 set_pconfig($uid,'facebook','no_linking', (($linkvalue) ? 0 : 1));
423
424                 // FB linkage was allowed but has just been turned off - remove all FB contacts and posts
425
426                 if((! intval($no_linking)) && (! intval($linkvalue))) {
427                         $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `network` = '%s' ",
428                                 intval($uid),
429                                 dbesc(NETWORK_FACEBOOK)
430                         );
431                         if(count($r)) {
432                                 require_once('include/Contact.php');
433                                 foreach($r as $rr)
434                                         contact_remove($rr['id']);
435                         }
436                 }
437                 elseif(intval($no_linking) && intval($linkvalue)) {
438                         // FB linkage is now allowed - import stuff.
439                         fb_get_self($uid);
440                         fb_get_friends($uid, true);
441                         fb_consume_all($uid);
442                 }
443
444                 info( t('Settings updated.') . EOL);
445         } 
446
447         return;         
448 }
449
450 // Facebook settings form
451
452 function facebook_content(&$a) {
453
454         if(! local_user()) {
455                 notice( t('Permission denied.') . EOL);
456                 return '';
457         }
458
459         if($a->argc > 1 && $a->argv[1] === 'remove') {
460                 del_pconfig(local_user(),'facebook','post');
461                 info( t('Facebook disabled') . EOL);
462         }
463
464         if($a->argc > 1 && $a->argv[1] === 'friends') {
465                 fb_get_friends(local_user(), true);
466                 info( t('Updating contacts') . EOL);
467         }
468
469
470         $fb_installed = get_pconfig(local_user(),'facebook','post');
471
472         $appid = get_config('facebook','appid');
473
474         if(! $appid) {
475                 notice( t('Facebook API key is missing.') . EOL);
476                 return '';
477         }
478
479         $a->page['htmlhead'] .= '<link rel="stylesheet" type="text/css" href="' 
480                 . $a->get_baseurl() . '/addon/facebook/facebook.css' . '" media="all" />' . "\r\n";
481
482         $o .= '<h3>' . t('Facebook Connect') . '</h3>';
483
484         if(! $fb_installed) { 
485                 $o .= '<div id="facebook-enable-wrapper">';
486
487                 $o .= '<a href="https://www.facebook.com/dialog/oauth?client_id=' . $appid . '&redirect_uri=' 
488                         . $a->get_baseurl() . '/facebook/' . $a->user['nickname'] . '&scope=publish_stream,read_stream,offline_access">' . t('Install Facebook connector for this account.') . '</a>';
489                 $o .= '</div>';
490         }
491
492         if($fb_installed) {
493                 $o .= '<div id="facebook-disable-wrapper">';
494
495                 $o .= '<a href="' . $a->get_baseurl() . '/facebook/remove' . '">' . t('Remove Facebook connector') . '</a></div>';
496
497                 $o .= '<div id="facebook-enable-wrapper">';
498
499                 $o .= '<a href="https://www.facebook.com/dialog/oauth?client_id=' . $appid . '&redirect_uri=' 
500                         . $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>';
501                 $o .= '</div>';
502         
503                 $o .= '<div id="facebook-post-default-form">';
504                 $o .= '<form action="facebook" method="post" >';
505                 $post_by_default = get_pconfig(local_user(),'facebook','post_by_default');
506                 $checked = (($post_by_default) ? ' checked="checked" ' : '');
507                 $o .= '<input type="checkbox" name="post_by_default" value="1"' . $checked . '/>' . ' ' . t('Post to Facebook by default') . EOL;
508
509                 $no_linking = get_pconfig(local_user(),'facebook','no_linking');
510                 $checked = (($no_linking) ? '' : ' checked="checked" ');
511                 $o .= '<input type="checkbox" name="facebook_linking" value="1"' . $checked . '/>' . ' ' . t('Link all your Facebook friends and conversations on this website') . EOL ;
512
513                 $o .= '<p>' . t('Facebook conversations consist of your <em>profile wall</em> and your friend <em>stream</em>.');
514                 $o .= ' ' . t('On this website, your Facebook friend stream is only visible to you.');
515                 $o .= ' ' . t('The following settings determine the privacy of your Facebook profile wall on this website.') . '</p>';
516
517                 $private_wall = get_pconfig(local_user(),'facebook','private_wall');
518                 $checked = (($private_wall) ? ' checked="checked" ' : '');
519                 $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 ;
520
521
522                 $no_wall = get_pconfig(local_user(),'facebook','no_wall');
523                 $checked = (($no_wall) ? ' checked="checked" ' : '');
524                 $o .= '<input type="checkbox" name="facebook_no_wall" value="1"' . $checked . '/>' . ' ' . t('Do not import your Facebook profile wall conversations') . EOL ;
525
526                 $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>';
527
528
529                 $blocked_apps = get_pconfig(local_user(),'facebook','blocked_apps');
530
531                 $o .= '<div><label id="blocked-apps-label" for="blocked-apps">' . t('Comma separated applications to ignore') . ' </label></div>';
532         $o .= '<div><textarea id="blocked-apps" name="blocked_apps" >' . htmlspecialchars($blocked_apps) . '</textarea></div>';
533
534                 $o .= '<input type="submit" name="submit" value="' . t('Submit') . '" /></form></div>';
535         }
536
537         return $o;
538 }
539
540
541
542 function facebook_cron($a,$b) {
543
544         $last = get_config('facebook','last_poll');
545         
546         $poll_interval = intval(get_config('facebook','poll_interval'));
547         if(! $poll_interval)
548                 $poll_interval = 3600;
549
550         if($last) {
551                 $next = $last + $poll_interval;
552                 if($next > time()) 
553                         return;
554         }
555
556         logger('facebook_cron');
557
558
559         // Find the FB users on this site and randomize in case one of them
560         // uses an obscene amount of memory. It may kill this queue run
561         // but hopefully we'll get a few others through on each run. 
562
563         $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'facebook' AND `k` = 'post' AND `v` = '1' ORDER BY RAND() ");
564         if(count($r)) {
565                 foreach($r as $rr) {
566                         if(get_pconfig($rr['uid'],'facebook','no_linking'))
567                                 continue;
568                         $ab = intval(get_config('system','account_abandon_days'));
569                         if($ab > 0) {
570                                 $z = q("SELECT `uid` FROM `user` WHERE `uid` = %d AND `login_date` > UTC_TIMESTAMP() - INTERVAL %d DAY LIMIT 1",
571                                         intval($rr['uid']),
572                                         intval($ab)
573                                 );
574                                 if(! count($z))
575                                         continue;
576                         }
577
578                         // check for new friends once a day
579                         $last_friend_check = get_pconfig($rr['uid'],'facebook','friend_check');
580                         if($last_friend_check) 
581                                 $next_friend_check = $last_friend_check + 86400;
582                         if($next_friend_check <= time()) {
583                                 fb_get_friends($rr['uid'], true);
584                                 set_pconfig($rr['uid'],'facebook','friend_check',time());
585                         }
586                         fb_consume_all($rr['uid']);
587                 }
588         }
589         
590         if (get_config('facebook', 'realtime_active') == 1) {
591                 if (!facebook_check_realtime_active()) {
592                         
593                         logger('facebook_cron: Facebook is not sending Real-Time Updates any more, although it is supposed to. Trying to fix it...', LOGGER_NORMAL);
594                         facebook_subscription_add_users();
595                         
596                         if (facebook_check_realtime_active()) 
597                                 logger('facebook_cron: Successful', LOGGER_NORMAL);
598                         else {
599                                 logger('facebook_cron: Failed', LOGGER_NORMAL);
600                                 
601                                 if(strlen($a->config['admin_email']) && !get_config('facebook', 'realtime_err_mailsent')) {
602                                         $res = mail($a->config['admin_email'], t('Problems with Facebook Real-Time Updates'), 
603                                                 "Hi!\n\nThere's a problem with the Facebook Real-Time Updates that cannot be solved automatically. Maybe an permission issue?\n\nThis e-mail will only be sent once.",
604                                                 'From: ' . t('Administrator') . '@' . $_SERVER['SERVER_NAME'] . "\n"
605                                                 . 'Content-type: text/plain; charset=UTF-8' . "\n"
606                                                 . 'Content-transfer-encoding: 8bit'
607                                         );
608                                         
609                                         set_config('facebook', 'realtime_err_mailsent', 1);
610                                 }
611                         }
612                 } else { // !facebook_check_realtime_active()
613                         del_config('facebook', 'realtime_err_mailsent');
614                 }
615         }
616         
617         set_config('facebook','last_poll', time());
618
619 }
620
621
622
623 function facebook_plugin_settings(&$a,&$b) {
624
625         $b .= '<div class="settings-block">';
626         $b .= '<h3>' . t('Facebook') . '</h3>';
627         $b .= '<a href="facebook">' . t('Facebook Connector Settings') . '</a><br />';
628         $b .= '</div>';
629
630 }
631
632
633 function facebook_plugin_admin(&$a, &$o){
634         
635         $activated = facebook_check_realtime_active();
636         if ($activated) {
637                 $o = t('Real-Time Updates are activated.') . '<br><br>';
638                 $o .= '<input type="submit" name="real_time_deactivate" value="' . t('Deactivate Real-Time Updates') . '">';
639         } else {
640                 $o = t('Real-Time Updates not activated.') . '<br><input type="submit" name="real_time_activate" value="' . t('Activate Real-Time Updates') . '">';
641         }
642 }
643
644 function facebook_plugin_admin_post(&$a, &$o){
645         if (x($_REQUEST,'real_time_activate')) {
646                 facebook_subscription_add_users();
647         }
648         if (x($_REQUEST,'real_time_deactivate')) {
649                 facebook_subscription_del_users();
650         }
651 }
652
653 function facebook_jot_nets(&$a,&$b) {
654         if(! local_user())
655                 return;
656
657         $fb_post = get_pconfig(local_user(),'facebook','post');
658         if(intval($fb_post) == 1) {
659                 $fb_defpost = get_pconfig(local_user(),'facebook','post_by_default');
660                 $selected = ((intval($fb_defpost) == 1) ? ' checked="checked" ' : '');
661                 $b .= '<div class="profile-jot-net"><input type="checkbox" name="facebook_enable"' . $selected . ' value="1" /> ' 
662                         . t('Post to Facebook') . '</div>';     
663         }
664 }
665
666
667 function facebook_post_hook(&$a,&$b) {
668
669
670         if($b['deleted'] || ($b['created'] !== $b['edited']))
671                 return;
672
673         /**
674          * Post to Facebook stream
675          */
676
677         require_once('include/group.php');
678         require_once('include/html2plain.php');
679
680         logger('Facebook post');
681
682         $reply = false;
683         $likes = false;
684
685         $toplevel = (($b['id'] == $b['parent']) ? true : false);
686
687
688         $linking = ((get_pconfig($b['uid'],'facebook','no_linking')) ? 0 : 1);
689
690         if((! $toplevel) && ($linking)) {
691                 $r = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
692                         intval($b['parent']),
693                         intval($b['uid'])
694                 );
695                 if(count($r) && substr($r[0]['uri'],0,4) === 'fb::')
696                         $reply = substr($r[0]['uri'],4);
697                 elseif(count($r) && substr($r[0]['extid'],0,4) === 'fb::')
698                         $reply = substr($r[0]['extid'],4);
699                 else
700                         return;
701
702                 $u = q("SELECT * FROM user where uid = %d limit 1",
703                         intval($b['uid'])
704                 );
705                 if(! count($u))
706                         return;
707
708                 // only accept comments from the item owner. Other contacts are unknown to FB.
709  
710                 if(! link_compare($b['author-link'], $a->get_baseurl() . '/profile/' . $u[0]['nickname']))
711                         return;
712                 
713
714                 logger('facebook reply id=' . $reply);
715         }
716
717         if(strstr($b['postopts'],'facebook') || ($b['private']) || ($reply)) {
718
719                 if($b['private'] && $reply === false) {
720                         $allow_people = expand_acl($b['allow_cid']);
721                         $allow_groups = expand_groups(expand_acl($b['allow_gid']));
722                         $deny_people  = expand_acl($b['deny_cid']);
723                         $deny_groups  = expand_groups(expand_acl($b['deny_gid']));
724
725                         $recipients = array_unique(array_merge($allow_people,$allow_groups));
726                         $deny = array_unique(array_merge($deny_people,$deny_groups));
727
728                         $allow_str = dbesc(implode(', ',$recipients));
729                         if($allow_str) {
730                                 $r = q("SELECT `notify` FROM `contact` WHERE `id` IN ( $allow_str ) AND `network` = 'face'"); 
731                                 $allow_arr = array();
732                                 if(count($r)) 
733                                         foreach($r as $rr)
734                                                 $allow_arr[] = $rr['notify'];
735                         }
736
737                         $deny_str = dbesc(implode(', ',$deny));
738                         if($deny_str) {
739                                 $r = q("SELECT `notify` FROM `contact` WHERE `id` IN ( $deny_str ) AND `network` = 'face'"); 
740                                 $deny_arr = array();
741                                 if(count($r)) 
742                                         foreach($r as $rr)
743                                                 $deny_arr[] = $rr['notify'];
744                         }
745
746                         if(count($deny_arr) && (! count($allow_arr))) {
747
748                                 // One or more FB folks were denied access but nobody on FB was specifically allowed access.
749                                 // This might cause the post to be open to public on Facebook, but only to selected members
750                                 // on another network. Since this could potentially leak a post to somebody who was denied, 
751                                 // we will skip posting it to Facebook with a slightly vague but relevant message that will 
752                                 // hopefully lead somebody to this code comment for a better explanation of what went wrong.
753
754                                 notice( t('Post to Facebook cancelled because of multi-network access permission conflict.') . EOL);
755                                 return;
756                         }
757
758
759                         // if it's a private message but no Facebook members are allowed or denied, skip Facebook post
760
761                         if((! count($allow_arr)) && (! count($deny_arr)))
762                                 return;
763                 }
764
765                 if($b['verb'] == ACTIVITY_LIKE)
766                         $likes = true;                          
767
768
769                 $appid  = get_config('facebook', 'appid'  );
770                 $secret = get_config('facebook', 'appsecret' );
771
772                 if($appid && $secret) {
773
774                         logger('facebook: have appid+secret');
775
776                         $fb_token  = get_pconfig($b['uid'],'facebook','access_token');
777
778
779                         // post to facebook if it's a public post and we've ticked the 'post to Facebook' box, 
780                         // or it's a private message with facebook participants
781                         // or it's a reply or likes action to an existing facebook post                 
782
783                         if($fb_token && ($toplevel || $b['private'] || $reply)) {
784                                 logger('facebook: able to post');
785                                 require_once('library/facebook.php');
786                                 require_once('include/bbcode.php');     
787
788                                 $msg = $b['body'];
789
790                                 logger('Facebook post: original msg=' . $msg, LOGGER_DATA);
791
792                                 // make links readable before we strip the code
793
794                                 // unless it's a dislike - just send the text as a comment
795
796                                 if($b['verb'] == ACTIVITY_DISLIKE)
797                                         $msg = trim(strip_tags(bbcode($msg)));
798
799                                 // Old code
800                                 /*$search_str = $a->get_baseurl() . '/search';
801
802                                 if(preg_match("/\[url=(.*?)\](.*?)\[\/url\]/is",$msg,$matches)) {
803
804                                         // don't use hashtags for message link
805
806                                         if(strpos($matches[2],$search_str) === false) {
807                                                 $link = $matches[1];
808                                                 if(substr($matches[2],0,5) != '[img]')
809                                                         $linkname = $matches[2];
810                                         }
811                                 }
812
813                                 // strip tag links to avoid link clutter, this really should be 
814                                 // configurable because we're losing information
815
816                                 $msg = preg_replace("/\#\[url=(.*?)\](.*?)\[\/url\]/is",'#$2',$msg);
817
818                                 // provide the link separately for normal links
819                                 $msg = preg_replace("/\[url=(.*?)\](.*?)\[\/url\]/is",'$2 $1',$msg);
820
821                                 if(preg_match("/\[img\](.*?)\[\/img\]/is",$msg,$matches))
822                                         $image = $matches[1];
823
824                                 $msg = preg_replace("/\[img\](.*?)\[\/img\]/is", t('Image: ') . '$1', $msg);
825
826                                 if((strpos($link,z_root()) !== false) && (! $image))
827                                         $image = $a->get_baseurl() . '/images/friendica-64.jpg';
828
829                                 $msg = trim(strip_tags(bbcode($msg)));*/
830
831                                 // New code
832
833                                 // Looking for the first image
834                                 $image = '';
835                                 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/is",$b['body'],$matches))
836                                         $image = $matches[3];
837
838                                 if ($image != '')
839                                         if(preg_match("/\[img\](.*?)\[\/img\]/is",$b['body'],$matches))
840                                                 $image = $matches[1];
841
842                                 // Checking for a bookmark element
843                                 $body = $b['body'];
844                                 if (strpos($body, "[bookmark") !== false) {
845                                         // splitting the text in two parts:
846                                         // before and after the bookmark
847                                         $pos = strpos($body, "[bookmark");
848                                         $body1 = substr($body, 0, $pos);
849                                         $body2 = substr($body, $pos);
850
851                                         // Removing the bookmark and all quotes after the bookmark
852                                         // they are mostly only the content after the bookmark.
853                                         $body2 = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism",'',$body2);
854                                         $body2 = preg_replace("/\[quote\=([^\]]*)\](.*?)\[\/quote\]/ism",'',$body2);
855                                         $body2 = preg_replace("/\[quote\](.*?)\[\/quote\]/ism",'',$body2);
856
857                                         $body = $body1.$body2;
858                                 }
859
860                                 // At first convert the text to html
861                                 $html = bbcode($body);
862
863                                 // Then convert it to plain text
864                                 $msg = trim($b['title']." \n\n".html2plain($html, 0, true));
865                                 $msg = html_entity_decode($msg,ENT_QUOTES,'UTF-8');
866
867                                 // Removing multiple newlines
868                                 while (strpos($msg, "\n\n\n") !== false)
869                                         $msg = str_replace("\n\n\n", "\n\n", $msg);
870
871                                 // add any attachments as text urls
872                                 $arr = explode(',',$b['attach']);
873
874                                 if(count($arr)) {
875                                         $msg .= "\n";
876                                         foreach($arr as $r) {
877                                                 $matches = false;
878                                                 $cnt = preg_match('|\[attach\]href=\"(.*?)\" size=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"\[\/attach\]|',$r,$matches);
879                                                 if($cnt) {
880                                                         $msg .= "\n".$matches[1];
881                                                 }
882                                         }
883                                 }
884
885                                 $link = '';
886                                 $linkname = '';
887                                 // look for bookmark-bbcode and handle it with priority
888                                 if(preg_match("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/is",$b['body'],$matches)) {
889                                         $link = $matches[1];
890                                         $linkname = $matches[2];
891                                 }
892
893                                 // If there is no bookmark element then take the first link
894                                 if ($link == '') {
895                                         $links = collecturls($html);
896                                         if (sizeof($links) > 0) {
897                                                 reset($links);
898                                                 $link = current($links);
899                                         }
900                                 }
901
902                                 // Remove trailing and leading spaces
903                                 $msg = trim($msg);
904
905                                 // Since facebook increased the maxpostlen massively this never should happen again :)
906                                 if (strlen($msg) > FACEBOOK_MAXPOSTLEN) {
907                                         $shortlink = "";
908                                         require_once('library/slinky.php');
909
910                                         $display_url = $b['plink'];
911
912                                         $slinky = new Slinky( $display_url );
913                                         // setup a cascade of shortening services
914                                         // try to get a short link from these services
915                                         // in the order ur1.ca, trim, id.gd, tinyurl
916                                         $slinky->set_cascade( array( new Slinky_UR1ca(), new Slinky_Trim(), new Slinky_IsGd(), new Slinky_TinyURL() ) );
917                                         $shortlink = $slinky->short();
918                                         // the new message will be shortened such that "... $shortlink"
919                                         // will fit into the character limit
920                                         $msg = substr($msg, 0, FACEBOOK_MAXPOSTLEN - strlen($shortlink) - 4);
921                                         $msg .= '... ' . $shortlink;
922                                 }
923
924                                 // Fallback - if message is empty
925                                 if(!strlen($msg))
926                                         $msg = $linkname;
927
928                                 if(!strlen($msg))
929                                         $msg = $link;
930
931                                 if(!strlen($msg))
932                                         $msg = $image;
933
934                                 // If there is nothing to post then exit
935                                 if(!strlen($msg))
936                                         return;
937
938                                 logger('Facebook post: msg=' . $msg, LOGGER_DATA);
939
940                                 if($likes) { 
941                                         $postvars = array('access_token' => $fb_token);
942                                 }
943                                 else {
944                                         $postvars = array(
945                                                 'access_token' => $fb_token, 
946                                                 'message' => $msg
947                                         );
948                                         if(isset($image))
949                                                 $postvars['picture'] = $image;
950                                         if(isset($link))
951                                                 $postvars['link'] = $link;
952                                         if(isset($linkname))
953                                                 $postvars['name'] = $linkname;
954                                 }
955
956                                 if(($b['private']) && ($toplevel)) {
957                                         $postvars['privacy'] = '{"value": "CUSTOM", "friends": "SOME_FRIENDS"';
958                                         if(count($allow_arr))
959                                                 $postvars['privacy'] .= ',"allow": "' . implode(',',$allow_arr) . '"';
960                                         if(count($deny_arr))
961                                                 $postvars['privacy'] .= ',"deny": "' . implode(',',$deny_arr) . '"';
962                                         $postvars['privacy'] .= '}';
963
964                                 }
965
966                                 if($reply) {
967                                         $url = 'https://graph.facebook.com/' . $reply . '/' . (($likes) ? 'likes' : 'comments');
968                                 }
969                                 else { 
970                                         $url = 'https://graph.facebook.com/me/feed';
971                                         if($b['plink'])
972                                                 $postvars['actions'] = '{"name": "' . t('View on Friendica') . '", "link": "' .  $b['plink'] . '"}';
973                                 }
974
975                                 logger('facebook: post to ' . $url);
976                                 logger('facebook: postvars: ' . print_r($postvars,true));
977
978                                 // "test_mode" prevents anything from actually being posted.
979                                 // Otherwise, let's do it.
980
981                                 if(! get_config('facebook','test_mode')) {
982                                         $x = post_url($url, $postvars);
983
984                                         $retj = json_decode($x);
985                                         if($retj->id) {
986                                                 q("UPDATE `item` SET `extid` = '%s' WHERE `id` = %d LIMIT 1",
987                                                         dbesc('fb::' . $retj->id),
988                                                         intval($b['id'])
989                                                 );
990                                                 del_pconfig($b['uid'], 'facebook', 'session_expired_mailsent');
991                                         }
992                                         else {
993                                                 if(! $likes) {
994                                                         $s = serialize(array('url' => $url, 'item' => $b['id'], 'post' => $postvars));
995                                                         require_once('include/queue_fn.php');
996                                                         add_to_queue($a->contact,NETWORK_FACEBOOK,$s);
997                                                         notice( t('Facebook post failed. Queued for retry.') . EOL);
998                                                 }
999                                                 
1000                                                 if (isset($retj->error) && $retj->error->type == "OAuthException" && $retj->error->code == 190) {
1001                                                         logger('Facebook session has expired due to changed password.', LOGGER_DEBUG);
1002                                                         if (!get_pconfig($b['uid'], 'facebook', 'session_expired_mailsent')) {
1003                                                                 require_once('include/enotify.php');
1004                                                         
1005                                                                 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", intval($b['uid']) );
1006                                                                 notification(array(
1007                                                                         'uid' => $b['uid'],
1008                                                                         'type' => NOTIFY_SYSTEM,
1009                                                                         'system_type' => 'facebook_connection_invalid',
1010                                                                         'language'     => $r[0]['language'],
1011                                                                         'to_name'      => $r[0]['username'],
1012                                                                         'to_email'     => $r[0]['email'],
1013                                                                 ));
1014                                                                 
1015                                                                 set_pconfig($b['uid'], 'facebook', 'session_expired_mailsent', '1');
1016                                                         }
1017                                                 }
1018                                         }
1019                                         
1020                                         logger('Facebook post returns: ' . $x, LOGGER_DEBUG);
1021                                 }
1022                         }
1023                 }
1024         }
1025 }
1026
1027 function facebook_enotify(&$app, &$data) {
1028         if (x($data, 'params') && $data['params']['type'] == NOTIFY_SYSTEM && x($data['params'], 'system_type') && $data['params']['system_type'] == 'facebook_connection_invalid') {
1029                 $data['itemlink'] = '/facebook';
1030                 $data['epreamble'] = $data['preamble'] = t('Your Facebook connection became invalid. Please Re-authenticate.');
1031                 $data['subject'] = t('Facebook connection became invalid');
1032         }
1033 }
1034
1035 function facebook_post_local(&$a,&$b) {
1036
1037         // Figure out if Facebook posting is enabled for this post and file it in 'postopts'
1038         // where we will discover it during background delivery.
1039
1040         // This can only be triggered by a local user posting to their own wall.
1041
1042         if((local_user()) && (local_user() == $b['uid'])) {
1043
1044                 $fb_post   = intval(get_pconfig(local_user(),'facebook','post'));
1045                 $fb_enable = (($fb_post && x($_REQUEST,'facebook_enable')) ? intval($_REQUEST['facebook_enable']) : 0);
1046
1047                 // if API is used, default to the chosen settings
1048                 if($_REQUEST['api_source'] && intval(get_pconfig(local_user(),'facebook','post_by_default')))
1049                         $fb_enable = 1;
1050
1051                 if(! $fb_enable)
1052                         return;
1053
1054                 if(strlen($b['postopts']))
1055                         $b['postopts'] .= ',';
1056                 $b['postopts'] .= 'facebook';
1057         }
1058 }
1059
1060
1061 function fb_queue_hook(&$a,&$b) {
1062
1063         $qi = q("SELECT * FROM `queue` WHERE `network` = '%s'",
1064                 dbesc(NETWORK_FACEBOOK)
1065         );
1066         if(! count($qi))
1067                 return;
1068
1069         require_once('include/queue_fn.php');
1070
1071         foreach($qi as $x) {
1072                 if($x['network'] !== NETWORK_FACEBOOK)
1073                         continue;
1074
1075                 logger('facebook_queue: run');
1076
1077                 $r = q("SELECT `user`.* FROM `user` LEFT JOIN `contact` on `contact`.`uid` = `user`.`uid` 
1078                         WHERE `contact`.`self` = 1 AND `contact`.`id` = %d LIMIT 1",
1079                         intval($x['cid'])
1080                 );
1081                 if(! count($r))
1082                         continue;
1083
1084                 $user = $r[0];
1085
1086                 $appid  = get_config('facebook', 'appid'  );
1087                 $secret = get_config('facebook', 'appsecret' );
1088
1089                 if($appid && $secret) {
1090                         $fb_post   = intval(get_pconfig($user['uid'],'facebook','post'));
1091                         $fb_token  = get_pconfig($user['uid'],'facebook','access_token');
1092
1093                         if($fb_post && $fb_token) {
1094                                 logger('facebook_queue: able to post');
1095                                 require_once('library/facebook.php');
1096
1097                                 $z = unserialize($x['content']);
1098                                 $item = $z['item'];
1099                                 $j = post_url($z['url'],$z['post']);
1100
1101                                 $retj = json_decode($j);
1102                                 if($retj->id) {
1103                                         q("UPDATE `item` SET `extid` = '%s' WHERE `id` = %d LIMIT 1",
1104                                                 dbesc('fb::' . $retj->id),
1105                                                 intval($item)
1106                                         );
1107                                         logger('facebook_queue: success: ' . $j); 
1108                                         remove_queue_item($x['id']);
1109                                 }
1110                                 else {
1111                                         logger('facebook_queue: failed: ' . $j);
1112                                         update_queue_time($x['id']);
1113                                 }
1114                         }
1115                 }
1116         }
1117 }
1118
1119 function fb_consume_all($uid) {
1120
1121         require_once('include/items.php');
1122
1123         $access_token = get_pconfig($uid,'facebook','access_token');
1124         if(! $access_token)
1125                 return;
1126         
1127         if(! get_pconfig($uid,'facebook','no_wall')) {
1128                 $private_wall = intval(get_pconfig($uid,'facebook','private_wall'));
1129                 $s = fetch_url('https://graph.facebook.com/me/feed?access_token=' . $access_token);
1130                 if($s) {
1131                         $j = json_decode($s);
1132                         if (isset($j->data)) {
1133                                 logger('fb_consume_stream: wall: ' . print_r($j,true), LOGGER_DATA);
1134                                 fb_consume_stream($uid,$j,($private_wall) ? false : true);
1135                         } else {
1136                                 logger('fb_consume_stream: wall: got no data from Facebook: ' . print_r($j,true), LOGGER_NORMAL);
1137                         }
1138                 }
1139         }
1140         $s = fetch_url('https://graph.facebook.com/me/home?access_token=' . $access_token);
1141         if($s) {
1142                 $j = json_decode($s);
1143                 if (isset($j->data)) {
1144                         logger('fb_consume_stream: feed: ' . print_r($j,true), LOGGER_DATA);
1145                         fb_consume_stream($uid,$j,false);
1146                 } else {
1147                         logger('fb_consume_stream: feed: got no data from Facebook: ' . print_r($j,true), LOGGER_NORMAL);
1148                 }
1149         }
1150
1151 }
1152
1153 function fb_get_photo($uid,$link) {
1154         $access_token = get_pconfig($uid,'facebook','access_token');
1155         if(! $access_token || (! stristr($link,'facebook.com/photo.php')))
1156                 return "\n" . '[url=' . $link . ']' . t('link') . '[/url]';
1157         $ret = preg_match('/fbid=([0-9]*)/',$link,$match);
1158         if($ret)
1159                 $photo_id = $match[1];
1160         $x = fetch_url('https://graph.facebook.com/' . $photo_id . '?access_token=' . $access_token);
1161         $j = json_decode($x);
1162         if($j->picture)
1163                 return "\n\n" . '[url=' . $link . '][img]' . $j->picture . '[/img][/url]';
1164         else
1165                 return "\n" . '[url=' . $link . ']' . t('link') . '[/url]';
1166 }
1167
1168 function fb_consume_stream($uid,$j,$wall = false) {
1169
1170         $a = get_app();
1171
1172
1173         $user = q("SELECT * FROM `user` WHERE `uid` = %d AND `account_expired` = 0 LIMIT 1",
1174                 intval($uid)
1175         );
1176         if(! count($user))
1177                 return;
1178
1179         $my_local_url = $a->get_baseurl() . '/profile/' . $user[0]['nickname'];
1180
1181         $no_linking = get_pconfig($uid,'facebook','no_linking');
1182         if($no_linking)
1183                 return;
1184
1185         $self = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
1186                 intval($uid)
1187         );
1188
1189         $blocked_apps = get_pconfig($uid,'facebook','blocked_apps');
1190         $blocked_apps_arr = explode(',',$blocked_apps);
1191
1192         $self_id = get_pconfig($uid,'facebook','self_id');
1193         if(! count($j->data) || (! strlen($self_id)))
1194                 return;
1195
1196         foreach($j->data as $entry) {
1197                 logger('fb_consume: entry: ' . print_r($entry,true), LOGGER_DATA);
1198                 $datarray = array();
1199
1200                 $r = q("SELECT * FROM `item` WHERE ( `uri` = '%s' OR `extid` = '%s') AND `uid` = %d LIMIT 1",
1201                                 dbesc('fb::' . $entry->id),
1202                                 dbesc('fb::' . $entry->id),
1203                                 intval($uid)
1204                 );
1205                 if(count($r)) {
1206                         $post_exists = true;
1207                         $orig_post = $r[0];
1208                         $top_item = $r[0]['id'];
1209                 }
1210                 else {
1211                         $post_exists = false;
1212                         $orig_post = null;
1213                 }
1214
1215                 if(! $orig_post) {
1216                         $datarray['gravity'] = 0;
1217                         $datarray['uid'] = $uid;
1218                         $datarray['wall'] = (($wall) ? 1 : 0);
1219                         $datarray['uri'] = $datarray['parent-uri'] = 'fb::' . $entry->id;
1220                         $from = $entry->from;
1221                         if($from->id == $self_id)
1222                                 $datarray['contact-id'] = $self[0]['id'];
1223                         else {
1224                                 $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d AND `blocked` = 0 AND `readonly` = 0 LIMIT 1",
1225                                         dbesc($from->id),
1226                                         intval($uid)
1227                                 );
1228                                 if(count($r))
1229                                         $datarray['contact-id'] = $r[0]['id'];
1230                         }
1231
1232                         // don't store post if we don't have a contact
1233
1234                         if(! x($datarray,'contact-id')) {
1235                                 logger('no contact: post ignored');
1236                                 continue;
1237                         }
1238
1239                         $datarray['verb'] = ACTIVITY_POST;
1240                         if($wall) {
1241                                 $datarray['owner-name'] = $self[0]['name'];
1242                                 $datarray['owner-link'] = $self[0]['url'];
1243                                 $datarray['owner-avatar'] = $self[0]['thumb'];
1244                         }
1245                         if(isset($entry->application) && isset($entry->application->name) && strlen($entry->application->name))
1246                                 $datarray['app'] = strip_tags($entry->application->name);
1247                         else
1248                                 $datarray['app'] = 'facebook';
1249
1250                         $found_blocked = false;
1251
1252                         if(count($blocked_apps_arr)) {
1253                                 foreach($blocked_apps_arr as $bad_appl) {
1254                                         if(strlen(trim($bad_appl)) && (stristr($datarray['app'],trim($bad_appl)))) {
1255                                                 $found_blocked = true;
1256                                         }
1257                                 }
1258                         }
1259                                 
1260                         if($found_blocked) {
1261                                 logger('facebook: blocking application: ' . $datarray['app']);
1262                                 continue;
1263                         }
1264
1265                         $datarray['author-name'] = $from->name;
1266                         $datarray['author-link'] = 'http://facebook.com/profile.php?id=' . $from->id;
1267                         $datarray['author-avatar'] = 'https://graph.facebook.com/' . $from->id . '/picture';
1268                         $datarray['plink'] = $datarray['author-link'] . '&v=wall&story_fbid=' . substr($entry->id,strpos($entry->id,'_') + 1);
1269
1270                         $datarray['body'] = escape_tags($entry->message);
1271
1272                         if($entry->picture && $entry->link) {
1273                                 $datarray['body'] .= "\n\n" . '[url=' . $entry->link . '][img]' . $entry->picture . '[/img][/url]';
1274                         }
1275                         else {
1276                                 if($entry->picture)
1277                                         $datarray['body'] .= "\n\n" . '[img]' . $entry->picture . '[/img]';
1278                                 // if just a link, it may be a wall photo - check
1279                                 if($entry->link)
1280                                         $datarray['body'] .= fb_get_photo($uid,$entry->link);
1281                         }
1282                         if($entry->name)
1283                                 $datarray['body'] .= "\n" . $entry->name;
1284                         if($entry->caption)
1285                                 $datarray['body'] .= "\n" . $entry->caption;
1286                         if($entry->description)
1287                                 $datarray['body'] .= "\n" . $entry->description;
1288                         $datarray['created'] = datetime_convert('UTC','UTC',$entry->created_time);
1289                         $datarray['edited'] = datetime_convert('UTC','UTC',$entry->updated_time);
1290
1291                         // If the entry has a privacy policy, we cannot assume who can or cannot see it,
1292                         // as the identities are from a foreign system. Mark it as private to the owner.
1293
1294                         if($entry->privacy && $entry->privacy->value !== 'EVERYONE') {
1295                                 $datarray['private'] = 1;
1296                                 $datarray['allow_cid'] = '<' . $self[0]['id'] . '>';
1297                         }
1298
1299                         if(trim($datarray['body']) == '') {
1300                                 logger('facebook: empty body');
1301                                 continue;
1302                         }
1303
1304                         $top_item = item_store($datarray);
1305                         $r = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1306                                 intval($top_item),
1307                                 intval($uid)
1308                         );
1309                         if(count($r)) {
1310                                 $orig_post = $r[0];
1311                                 logger('fb: new top level item posted');
1312                         }
1313                 }
1314
1315                 if(isset($entry->likes) && isset($entry->likes->data))
1316                         $likers = $entry->likes->data;
1317                 else
1318                         $likers = null;
1319
1320                 if(isset($entry->comments) && isset($entry->comments->data))
1321                         $comments = $entry->comments->data;
1322                 else
1323                         $comments = null;
1324
1325                 if(is_array($likers)) {
1326                         foreach($likers as $likes) {
1327
1328                                 if(! $orig_post)
1329                                         continue;
1330
1331                                 // If we posted the like locally, it will be found with our url, not the FB url.
1332
1333                                 $second_url = (($likes->id == $self_id) ? $self[0]['url'] : 'http://facebook.com/profile.php?id=' . $likes->id); 
1334
1335                                 $r = q("SELECT * FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `verb` = '%s' 
1336                                         AND ( `author-link` = '%s' OR `author-link` = '%s' ) LIMIT 1",
1337                                         dbesc($orig_post['uri']),
1338                                         intval($uid),
1339                                         dbesc(ACTIVITY_LIKE),
1340                                         dbesc('http://facebook.com/profile.php?id=' . $likes->id),
1341                                         dbesc($second_url)
1342                                 );
1343
1344                                 if(count($r))
1345                                         continue;
1346                                         
1347                                 $likedata = array();
1348                                 $likedata['parent'] = $top_item;
1349                                 $likedata['verb'] = ACTIVITY_LIKE;
1350                                 $likedata['gravity'] = 3;
1351                                 $likedata['uid'] = $uid;
1352                                 $likedata['wall'] = (($wall) ? 1 : 0);
1353                                 $likedata['uri'] = item_new_uri($a->get_baseurl(), $uid);
1354                                 $likedata['parent-uri'] = $orig_post['uri'];
1355                                 if($likes->id == $self_id)
1356                                         $likedata['contact-id'] = $self[0]['id'];
1357                                 else {
1358                                         $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d AND `blocked` = 0 AND `readonly` = 0 LIMIT 1",
1359                                                 dbesc($likes->id),
1360                                                 intval($uid)
1361                                         );
1362                                         if(count($r))
1363                                                 $likedata['contact-id'] = $r[0]['id'];
1364                                 }
1365                                 if(! x($likedata,'contact-id'))
1366                                         $likedata['contact-id'] = $orig_post['contact-id'];
1367
1368                                 $likedata['app'] = 'facebook';
1369                                 $likedata['verb'] = ACTIVITY_LIKE;                                              
1370                                 $likedata['author-name'] = $likes->name;
1371                                 $likedata['author-link'] = 'http://facebook.com/profile.php?id=' . $likes->id;
1372                                 $likedata['author-avatar'] = 'https://graph.facebook.com/' . $likes->id . '/picture';
1373                                 
1374                                 $author  = '[url=' . $likedata['author-link'] . ']' . $likedata['author-name'] . '[/url]';
1375                                 $objauthor =  '[url=' . $orig_post['author-link'] . ']' . $orig_post['author-name'] . '[/url]';
1376                                 $post_type = t('status');
1377                         $plink = '[url=' . $orig_post['plink'] . ']' . $post_type . '[/url]';
1378                                 $likedata['object-type'] = ACTIVITY_OBJ_NOTE;
1379
1380                                 $likedata['body'] = sprintf( t('%1$s likes %2$s\'s %3$s'), $author, $objauthor, $plink);
1381                                 $likedata['object'] = '<object><type>' . ACTIVITY_OBJ_NOTE . '</type><local>1</local>' . 
1382                                         '<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>';  
1383
1384                                 $item = item_store($likedata);                  
1385                         }
1386                 }
1387                 if(is_array($comments)) {
1388                         foreach($comments as $cmnt) {
1389
1390                                 if(! $orig_post)
1391                                         continue;
1392
1393                                 $r = q("SELECT * FROM `item` WHERE `uid` = %d AND ( `uri` = '%s' OR `extid` = '%s' ) LIMIT 1",
1394                                         intval($uid),
1395                                         dbesc('fb::' . $cmnt->id),
1396                                         dbesc('fb::' . $cmnt->id)
1397                                 );
1398                                 if(count($r))
1399                                         continue;
1400
1401                                 $cmntdata = array();
1402                                 $cmntdata['parent'] = $top_item;
1403                                 $cmntdata['verb'] = ACTIVITY_POST;
1404                                 $cmntdata['gravity'] = 6;
1405                                 $cmntdata['uid'] = $uid;
1406                                 $cmntdata['wall'] = (($wall) ? 1 : 0);
1407                                 $cmntdata['uri'] = 'fb::' . $cmnt->id;
1408                                 $cmntdata['parent-uri'] = $orig_post['uri'];
1409                                 if($cmnt->from->id == $self_id) {
1410                                         $cmntdata['contact-id'] = $self[0]['id'];
1411                                 }
1412                                 else {
1413                                         $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d LIMIT 1",
1414                                                 dbesc($cmnt->from->id),
1415                                                 intval($uid)
1416                                         );
1417                                         if(count($r)) {
1418                                                 $cmntdata['contact-id'] = $r[0]['id'];
1419                                                 if($r[0]['blocked'] || $r[0]['readonly'])
1420                                                         continue;
1421                                         }
1422                                 }
1423                                 if(! x($cmntdata,'contact-id'))
1424                                         $cmntdata['contact-id'] = $orig_post['contact-id'];
1425
1426                                 $cmntdata['app'] = 'facebook';
1427                                 $cmntdata['created'] = datetime_convert('UTC','UTC',$cmnt->created_time);
1428                                 $cmntdata['edited']  = datetime_convert('UTC','UTC',$cmnt->created_time);
1429                                 $cmntdata['verb'] = ACTIVITY_POST;                                              
1430                                 $cmntdata['author-name'] = $cmnt->from->name;
1431                                 $cmntdata['author-link'] = 'http://facebook.com/profile.php?id=' . $cmnt->from->id;
1432                                 $cmntdata['author-avatar'] = 'https://graph.facebook.com/' . $cmnt->from->id . '/picture';
1433                                 $cmntdata['body'] = $cmnt->message;
1434                                 $item = item_store($cmntdata);                  
1435                                 
1436                                 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 ",
1437                                         dbesc($orig_post['uri']),
1438                                         intval($uid)
1439                                 );
1440
1441                                 if(count($myconv)) {
1442                                         $importer_url = $a->get_baseurl() . '/profile/' . $user[0]['nickname'];
1443
1444                                         foreach($myconv as $conv) {
1445
1446                                                 // now if we find a match, it means we're in this conversation
1447         
1448                                                 if(! link_compare($conv['author-link'],$importer_url))
1449                                                         continue;
1450
1451                                                 require_once('include/enotify.php');
1452                                                                 
1453                                                 $conv_parent = $conv['parent'];
1454
1455                                                 notification(array(
1456                                                         'type'         => NOTIFY_COMMENT,
1457                                                         'notify_flags' => $user[0]['notify-flags'],
1458                                                         'language'     => $user[0]['language'],
1459                                                         'to_name'      => $user[0]['username'],
1460                                                         'to_email'     => $user[0]['email'],
1461                                                         'uid'          => $user[0]['uid'],
1462                                                         'item'         => $cmntdata,
1463                                                         'link'             => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $item,
1464                                                         'source_name'  => $cmntdata['author-name'],
1465                                                         'source_link'  => $cmntdata['author-link'],
1466                                                         'source_photo' => $cmntdata['author-avatar'],
1467                                                         'verb'         => ACTIVITY_POST,
1468                                                         'otype'        => 'item',
1469                                                         'parent'       => $conv_parent,
1470                                                 ));
1471
1472                                                 // only send one notification
1473                                                 break;
1474                                         }
1475                                 }
1476                         }
1477                 }
1478         }
1479 }
1480
1481
1482 function fb_get_app_access_token() {
1483         
1484         $acc_token = get_config('facebook','app_access_token');
1485         
1486         if ($acc_token !== false) return $acc_token;
1487         
1488         $appid = get_config('facebook','appid');
1489         $appsecret = get_config('facebook', 'appsecret');
1490         
1491         if ($appid === false || $appsecret === false) {
1492                 logger('fb_get_app_access_token: appid and/or appsecret not set', LOGGER_DEBUG);
1493                 return false;
1494         }
1495         
1496         $x = fetch_url('https://graph.facebook.com/oauth/access_token?client_id=' . $appid . '&client_secret=' . $appsecret . "&grant_type=client_credentials");
1497         
1498         if(strpos($x,'access_token=') !== false) {
1499                 logger('fb_get_app_access_token: returned access token: ' . $x, LOGGER_DATA);
1500         
1501                 $token = str_replace('access_token=', '', $x);
1502                 if(strpos($token,'&') !== false)
1503                         $token = substr($token,0,strpos($token,'&'));
1504                 
1505                 if ($token == "") {
1506                         logger('fb_get_app_access_token: empty token: ' . $x, LOGGER_DEBUG);
1507                         return false;
1508                 }
1509                 set_config('facebook','app_access_token',$token);
1510                 return $token;
1511         } else {
1512                 logger('fb_get_app_access_token: response did not contain an access_token: ' . $x, LOGGER_DATA);
1513                 return false;
1514         }
1515 }
1516
1517 function facebook_subscription_del_users() {
1518         $a = get_app();
1519         $access_token = fb_get_app_access_token();
1520         
1521         $url = "https://graph.facebook.com/" . get_config('facebook', 'appid'  ) . "/subscriptions?access_token=" . $access_token;
1522         facebook_delete_url($url);
1523         
1524         del_config('facebook', 'realtime_active');
1525 }
1526
1527 function facebook_subscription_add_users() {
1528         
1529         $a = get_app();
1530         $access_token = fb_get_app_access_token();
1531         
1532         $url = "https://graph.facebook.com/" . get_config('facebook', 'appid'  ) . "/subscriptions?access_token=" . $access_token;
1533         
1534         list($usec, $sec) = explode(" ", microtime());
1535         $verify_token = sha1($usec . $sec . rand(0, 999999999));
1536         set_config('facebook', 'cb_verify_token', $verify_token);
1537         
1538         $cb = $a->get_baseurl() . '/facebook/?realtime_cb=1';
1539         
1540         $j = post_url($url,array(
1541                 "object" => "user",
1542                 "fields" => "feed,friends",
1543                 "callback_url" => $cb,
1544                 "verify_token" => $verify_token,
1545         ));
1546         del_config('facebook', 'cb_verify_token');
1547         
1548         if ($j) {
1549                 logger("Facebook reponse: " . $j, LOGGER_DATA);
1550                 
1551                 if (facebook_check_realtime_active()) set_config('facebook', 'realtime_active', 1);
1552         };
1553 }
1554
1555 function facebook_subscriptions_get() {
1556         
1557         $access_token = fb_get_app_access_token();
1558         if (!$access_token) return null;
1559         
1560         $url = "https://graph.facebook.com/" . get_config('facebook', 'appid'  ) . "/subscriptions?access_token=" . $access_token;
1561         $j = fetch_url($url);
1562         $ret = null;
1563         if ($j) {
1564                 $x = json_decode($j);
1565                 if (isset($x->data)) $ret = $x->data;
1566         }
1567         return $ret;
1568 }
1569
1570
1571 function facebook_check_realtime_active() {
1572         $ret = facebook_subscriptions_get();
1573         if (is_null($ret)) return false;
1574         if (is_array($ret)) foreach ($ret as $re) if (is_object($re) && $re->object == "user") return true;
1575         return false;
1576 }
1577
1578
1579
1580
1581 // DELETE-request to $url
1582
1583 if(! function_exists('facebook_delete_url')) {
1584 function facebook_delete_url($url,$headers = null, &$redirects = 0, $timeout = 0) {
1585         $a = get_app();
1586         $ch = curl_init($url);
1587         if(($redirects > 8) || (! $ch)) 
1588                 return false;
1589
1590         curl_setopt($ch, CURLOPT_HEADER, true);
1591         curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
1592         curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
1593         curl_setopt($ch, CURLOPT_USERAGENT, "Friendica");
1594
1595         if(intval($timeout)) {
1596                 curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
1597         }
1598         else {
1599                 $curl_time = intval(get_config('system','curl_timeout'));
1600                 curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
1601         }
1602
1603         if(defined('LIGHTTPD')) {
1604                 if(!is_array($headers)) {
1605                         $headers = array('Expect:');
1606                 } else {
1607                         if(!in_array('Expect:', $headers)) {
1608                                 array_push($headers, 'Expect:');
1609                         }
1610                 }
1611         }
1612         if($headers)
1613                 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
1614
1615         $check_cert = get_config('system','verifyssl');
1616         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
1617         $prx = get_config('system','proxy');
1618         if(strlen($prx)) {
1619                 curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
1620                 curl_setopt($ch, CURLOPT_PROXY, $prx);
1621                 $prxusr = get_config('system','proxyuser');
1622                 if(strlen($prxusr))
1623                         curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
1624         }
1625
1626         $a->set_curl_code(0);
1627
1628         // don't let curl abort the entire application
1629         // if it throws any errors.
1630
1631         $s = @curl_exec($ch);
1632
1633         $base = $s;
1634         $curl_info = curl_getinfo($ch);
1635         $http_code = $curl_info['http_code'];
1636
1637         $header = '';
1638
1639         // Pull out multiple headers, e.g. proxy and continuation headers
1640         // allow for HTTP/2.x without fixing code
1641
1642         while(preg_match('/^HTTP\/[1-2].+? [1-5][0-9][0-9]/',$base)) {
1643                 $chunk = substr($base,0,strpos($base,"\r\n\r\n")+4);
1644                 $header .= $chunk;
1645                 $base = substr($base,strlen($chunk));
1646         }
1647
1648         if($http_code == 301 || $http_code == 302 || $http_code == 303) {
1649         $matches = array();
1650         preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
1651         $url = trim(array_pop($matches));
1652         $url_parsed = @parse_url($url);
1653         if (isset($url_parsed)) {
1654             $redirects++;
1655             return delete_url($url,$headers,$redirects,$timeout);
1656         }
1657     }
1658         $a->set_curl_code($http_code);
1659         $body = substr($s,strlen($header));
1660
1661         $a->set_curl_headers($header);
1662
1663         curl_close($ch);
1664         return($body);
1665 }}