]> git.mxchange.org Git - friendica-addons.git/blob - facebook/facebook.php
Maximum limit for facebook is now max higher than 400.
[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                                 /*$search_str = $a->get_baseurl() . '/search';
800
801                                 if(preg_match("/\[url=(.*?)\](.*?)\[\/url\]/is",$msg,$matches)) {
802
803                                         // don't use hashtags for message link
804
805                                         if(strpos($matches[2],$search_str) === false) {
806                                                 $link = $matches[1];
807                                                 if(substr($matches[2],0,5) != '[img]')
808                                                         $linkname = $matches[2];
809                                         }
810                                 }
811
812                                 // strip tag links to avoid link clutter, this really should be 
813                                 // configurable because we're losing information
814
815                                 $msg = preg_replace("/\#\[url=(.*?)\](.*?)\[\/url\]/is",'#$2',$msg);
816
817                                 // provide the link separately for normal links
818                                 $msg = preg_replace("/\[url=(.*?)\](.*?)\[\/url\]/is",'$2 $1',$msg);
819
820                                 if(preg_match("/\[img\](.*?)\[\/img\]/is",$msg,$matches))
821                                         $image = $matches[1];
822
823                                 $msg = preg_replace("/\[img\](.*?)\[\/img\]/is", t('Image: ') . '$1', $msg);
824
825                                 if((strpos($link,z_root()) !== false) && (! $image))
826                                         $image = $a->get_baseurl() . '/images/friendica-64.jpg';
827
828                                 $msg = trim(strip_tags(bbcode($msg)));*/
829
830                                 // Test
831
832                                 // Looking for images
833                                 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/is",$b['body'],$matches))
834                                         $image = $matches[3];
835
836                                 if(preg_match("/\[img\](.*?)\[\/img\]/is",$b['body'],$matches))
837                                         $image = $matches[1];
838
839                                 // Replace bookmark with url
840                                 $body = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism",'[url=$1]$2[/url]',$b['body']);
841
842                                 $html = bbcode($body);
843                                 $msg = trim($b['title']." \n".html2plain($html, 0, true));
844                                 $msg = html_entity_decode($msg,ENT_QUOTES,'UTF-8');
845
846                                 $toolong = false;
847
848                                 // add any attachments as text urls
849
850                                 $arr = explode(',',$b['attach']);
851
852                                 if(count($arr)) {
853                                         $msg .= "\n";
854                                         foreach($arr as $r) {
855                                                 $matches = false;
856                                                 $cnt = preg_match('|\[attach\]href=\"(.*?)\" size=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"\[\/attach\]|',$r,$matches);
857                                                 if($cnt) {
858                                                         $msg .= "\n".$matches[1];
859                                                 }
860                                         }
861                                 }
862
863                                 $link = '';
864                                 // look for bookmark-bbcode and handle it with priority
865                                 if(preg_match("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/is",$b['body'],$matches))
866                                         $link = $matches[1];
867
868                                 if ($link == '') {
869                                         $links = collecturls($html);
870                                         if (sizeof($links) > 0) {
871                                                 reset($links);
872                                                 $link = current($links);
873                                                 /*if (strlen($msg."\n".$link) <= FACEBOOK_MAXPOSTLEN)
874                                                         $msg .= "\n".$link;
875                                                 else
876                                                         $toolong = true;*/
877                                         }
878                                 }
879
880                                 if ((strlen($msg) > FACEBOOK_MAXPOSTLEN) or $toolong) {
881                                         $shortlink = "";
882                                         require_once('library/slinky.php');
883
884                                         $display_url = $b['plink'];
885
886                                         $slinky = new Slinky( $display_url );
887                                         // setup a cascade of shortening services
888                                         // try to get a short link from these services
889                                         // in the order ur1.ca, trim, id.gd, tinyurl
890                                         $slinky->set_cascade( array( new Slinky_UR1ca(), new Slinky_Trim(), new Slinky_IsGd(), new Slinky_TinyURL() ) );
891                                         $shortlink = $slinky->short();
892                                         // the new message will be shortened such that "... $shortlink"
893                                         // will fit into the character limit
894                                         $msg = substr($msg, 0, FACEBOOK_MAXPOSTLEN - strlen($shortlink) - 4);
895                                         $msg .= '... ' . $shortlink;
896                                 }
897
898                                 if(!strlen($msg) and !strlen($link) and !strlen($image))
899                                         return;
900
901                                 logger('Facebook post: msg=' . $msg, LOGGER_DATA);
902
903                                 if($likes) { 
904                                         $postvars = array('access_token' => $fb_token);
905                                 }
906                                 else {
907                                         $postvars = array(
908                                                 'access_token' => $fb_token, 
909                                                 'message' => $msg
910                                         );
911                                         if(isset($image))
912                                                 $postvars['picture'] = $image;
913                                         if(isset($link))
914                                                 $postvars['link'] = $link;
915                                         if(isset($linkname))
916                                                 $postvars['name'] = $linkname;
917                                 }
918
919                                 if(($b['private']) && ($toplevel)) {
920                                         $postvars['privacy'] = '{"value": "CUSTOM", "friends": "SOME_FRIENDS"';
921                                         if(count($allow_arr))
922                                                 $postvars['privacy'] .= ',"allow": "' . implode(',',$allow_arr) . '"';
923                                         if(count($deny_arr))
924                                                 $postvars['privacy'] .= ',"deny": "' . implode(',',$deny_arr) . '"';
925                                         $postvars['privacy'] .= '}';
926
927                                 }
928
929                                 if($reply) {
930                                         $url = 'https://graph.facebook.com/' . $reply . '/' . (($likes) ? 'likes' : 'comments');
931                                 }
932                                 else { 
933                                         $url = 'https://graph.facebook.com/me/feed';
934                                         if($b['plink'])
935                                                 $postvars['actions'] = '{"name": "' . t('View on Friendica') . '", "link": "' .  $b['plink'] . '"}';
936                                 }
937
938                                 logger('facebook: post to ' . $url);
939                                 logger('facebook: postvars: ' . print_r($postvars,true));
940
941                                 // "test_mode" prevents anything from actually being posted.
942                                 // Otherwise, let's do it.
943
944                                 if(! get_config('facebook','test_mode')) {
945                                         $x = post_url($url, $postvars);
946
947                                         $retj = json_decode($x);
948                                         if($retj->id) {
949                                                 q("UPDATE `item` SET `extid` = '%s' WHERE `id` = %d LIMIT 1",
950                                                         dbesc('fb::' . $retj->id),
951                                                         intval($b['id'])
952                                                 );
953                                                 del_pconfig($b['uid'], 'facebook', 'session_expired_mailsent');
954                                         }
955                                         else {
956                                                 if(! $likes) {
957                                                         $s = serialize(array('url' => $url, 'item' => $b['id'], 'post' => $postvars));
958                                                         require_once('include/queue_fn.php');
959                                                         add_to_queue($a->contact,NETWORK_FACEBOOK,$s);
960                                                         notice( t('Facebook post failed. Queued for retry.') . EOL);
961                                                 }
962                                                 
963                                                 if (isset($retj->error) && $retj->error->type == "OAuthException" && $retj->error->code == 190) {
964                                                         logger('Facebook session has expired due to changed password.', LOGGER_DEBUG);
965                                                         if (!get_pconfig($b['uid'], 'facebook', 'session_expired_mailsent')) {
966                                                                 require_once('include/enotify.php');
967                                                         
968                                                                 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", intval($b['uid']) );
969                                                                 notification(array(
970                                                                         'uid' => $b['uid'],
971                                                                         'type' => NOTIFY_SYSTEM,
972                                                                         'system_type' => 'facebook_connection_invalid',
973                                                                         'language'     => $r[0]['language'],
974                                                                         'to_name'      => $r[0]['username'],
975                                                                         'to_email'     => $r[0]['email'],
976                                                                 ));
977                                                                 
978                                                                 set_pconfig($b['uid'], 'facebook', 'session_expired_mailsent', '1');
979                                                         }
980                                                 }
981                                         }
982                                         
983                                         logger('Facebook post returns: ' . $x, LOGGER_DEBUG);
984                                 }
985                         }
986                 }
987         }
988 }
989
990 function facebook_enotify(&$app, &$data) {
991         if (x($data, 'params') && $data['params']['type'] == NOTIFY_SYSTEM && x($data['params'], 'system_type') && $data['params']['system_type'] == 'facebook_connection_invalid') {
992                 $data['itemlink'] = '/facebook';
993                 $data['epreamble'] = $data['preamble'] = t('Your Facebook connection became invalid. Please Re-authenticate.');
994                 $data['subject'] = t('Facebook connection became invalid');
995         }
996 }
997
998 function facebook_post_local(&$a,&$b) {
999
1000         // Figure out if Facebook posting is enabled for this post and file it in 'postopts'
1001         // where we will discover it during background delivery.
1002
1003         // This can only be triggered by a local user posting to their own wall.
1004
1005         if((local_user()) && (local_user() == $b['uid'])) {
1006
1007                 $fb_post   = intval(get_pconfig(local_user(),'facebook','post'));
1008                 $fb_enable = (($fb_post && x($_REQUEST,'facebook_enable')) ? intval($_REQUEST['facebook_enable']) : 0);
1009
1010                 // if API is used, default to the chosen settings
1011                 if($_REQUEST['api_source'] && intval(get_pconfig(local_user(),'facebook','post_by_default')))
1012                         $fb_enable = 1;
1013
1014                 if(! $fb_enable)
1015                         return;
1016
1017                 if(strlen($b['postopts']))
1018                         $b['postopts'] .= ',';
1019                 $b['postopts'] .= 'facebook';
1020         }
1021 }
1022
1023
1024 function fb_queue_hook(&$a,&$b) {
1025
1026         $qi = q("SELECT * FROM `queue` WHERE `network` = '%s'",
1027                 dbesc(NETWORK_FACEBOOK)
1028         );
1029         if(! count($qi))
1030                 return;
1031
1032         require_once('include/queue_fn.php');
1033
1034         foreach($qi as $x) {
1035                 if($x['network'] !== NETWORK_FACEBOOK)
1036                         continue;
1037
1038                 logger('facebook_queue: run');
1039
1040                 $r = q("SELECT `user`.* FROM `user` LEFT JOIN `contact` on `contact`.`uid` = `user`.`uid` 
1041                         WHERE `contact`.`self` = 1 AND `contact`.`id` = %d LIMIT 1",
1042                         intval($x['cid'])
1043                 );
1044                 if(! count($r))
1045                         continue;
1046
1047                 $user = $r[0];
1048
1049                 $appid  = get_config('facebook', 'appid'  );
1050                 $secret = get_config('facebook', 'appsecret' );
1051
1052                 if($appid && $secret) {
1053                         $fb_post   = intval(get_pconfig($user['uid'],'facebook','post'));
1054                         $fb_token  = get_pconfig($user['uid'],'facebook','access_token');
1055
1056                         if($fb_post && $fb_token) {
1057                                 logger('facebook_queue: able to post');
1058                                 require_once('library/facebook.php');
1059
1060                                 $z = unserialize($x['content']);
1061                                 $item = $z['item'];
1062                                 $j = post_url($z['url'],$z['post']);
1063
1064                                 $retj = json_decode($j);
1065                                 if($retj->id) {
1066                                         q("UPDATE `item` SET `extid` = '%s' WHERE `id` = %d LIMIT 1",
1067                                                 dbesc('fb::' . $retj->id),
1068                                                 intval($item)
1069                                         );
1070                                         logger('facebook_queue: success: ' . $j); 
1071                                         remove_queue_item($x['id']);
1072                                 }
1073                                 else {
1074                                         logger('facebook_queue: failed: ' . $j);
1075                                         update_queue_time($x['id']);
1076                                 }
1077                         }
1078                 }
1079         }
1080 }
1081
1082 function fb_consume_all($uid) {
1083
1084         require_once('include/items.php');
1085
1086         $access_token = get_pconfig($uid,'facebook','access_token');
1087         if(! $access_token)
1088                 return;
1089         
1090         if(! get_pconfig($uid,'facebook','no_wall')) {
1091                 $private_wall = intval(get_pconfig($uid,'facebook','private_wall'));
1092                 $s = fetch_url('https://graph.facebook.com/me/feed?access_token=' . $access_token);
1093                 if($s) {
1094                         $j = json_decode($s);
1095                         if (isset($j->data)) {
1096                                 logger('fb_consume_stream: wall: ' . print_r($j,true), LOGGER_DATA);
1097                                 fb_consume_stream($uid,$j,($private_wall) ? false : true);
1098                         } else {
1099                                 logger('fb_consume_stream: wall: got no data from Facebook: ' . print_r($j,true), LOGGER_NORMAL);
1100                         }
1101                 }
1102         }
1103         $s = fetch_url('https://graph.facebook.com/me/home?access_token=' . $access_token);
1104         if($s) {
1105                 $j = json_decode($s);
1106                 if (isset($j->data)) {
1107                         logger('fb_consume_stream: feed: ' . print_r($j,true), LOGGER_DATA);
1108                         fb_consume_stream($uid,$j,false);
1109                 } else {
1110                         logger('fb_consume_stream: feed: got no data from Facebook: ' . print_r($j,true), LOGGER_NORMAL);
1111                 }
1112         }
1113
1114 }
1115
1116 function fb_get_photo($uid,$link) {
1117         $access_token = get_pconfig($uid,'facebook','access_token');
1118         if(! $access_token || (! stristr($link,'facebook.com/photo.php')))
1119                 return "\n" . '[url=' . $link . ']' . t('link') . '[/url]';
1120         $ret = preg_match('/fbid=([0-9]*)/',$link,$match);
1121         if($ret)
1122                 $photo_id = $match[1];
1123         $x = fetch_url('https://graph.facebook.com/' . $photo_id . '?access_token=' . $access_token);
1124         $j = json_decode($x);
1125         if($j->picture)
1126                 return "\n\n" . '[url=' . $link . '][img]' . $j->picture . '[/img][/url]';
1127         else
1128                 return "\n" . '[url=' . $link . ']' . t('link') . '[/url]';
1129 }
1130
1131 function fb_consume_stream($uid,$j,$wall = false) {
1132
1133         $a = get_app();
1134
1135
1136         $user = q("SELECT * FROM `user` WHERE `uid` = %d AND `account_expired` = 0 LIMIT 1",
1137                 intval($uid)
1138         );
1139         if(! count($user))
1140                 return;
1141
1142         $my_local_url = $a->get_baseurl() . '/profile/' . $user[0]['nickname'];
1143
1144         $no_linking = get_pconfig($uid,'facebook','no_linking');
1145         if($no_linking)
1146                 return;
1147
1148         $self = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
1149                 intval($uid)
1150         );
1151
1152         $blocked_apps = get_pconfig($uid,'facebook','blocked_apps');
1153         $blocked_apps_arr = explode(',',$blocked_apps);
1154
1155         $self_id = get_pconfig($uid,'facebook','self_id');
1156         if(! count($j->data) || (! strlen($self_id)))
1157                 return;
1158
1159         foreach($j->data as $entry) {
1160                 logger('fb_consume: entry: ' . print_r($entry,true), LOGGER_DATA);
1161                 $datarray = array();
1162
1163                 $r = q("SELECT * FROM `item` WHERE ( `uri` = '%s' OR `extid` = '%s') AND `uid` = %d LIMIT 1",
1164                                 dbesc('fb::' . $entry->id),
1165                                 dbesc('fb::' . $entry->id),
1166                                 intval($uid)
1167                 );
1168                 if(count($r)) {
1169                         $post_exists = true;
1170                         $orig_post = $r[0];
1171                         $top_item = $r[0]['id'];
1172                 }
1173                 else {
1174                         $post_exists = false;
1175                         $orig_post = null;
1176                 }
1177
1178                 if(! $orig_post) {
1179                         $datarray['gravity'] = 0;
1180                         $datarray['uid'] = $uid;
1181                         $datarray['wall'] = (($wall) ? 1 : 0);
1182                         $datarray['uri'] = $datarray['parent-uri'] = 'fb::' . $entry->id;
1183                         $from = $entry->from;
1184                         if($from->id == $self_id)
1185                                 $datarray['contact-id'] = $self[0]['id'];
1186                         else {
1187                                 $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d AND `blocked` = 0 AND `readonly` = 0 LIMIT 1",
1188                                         dbesc($from->id),
1189                                         intval($uid)
1190                                 );
1191                                 if(count($r))
1192                                         $datarray['contact-id'] = $r[0]['id'];
1193                         }
1194
1195                         // don't store post if we don't have a contact
1196
1197                         if(! x($datarray,'contact-id')) {
1198                                 logger('no contact: post ignored');
1199                                 continue;
1200                         }
1201
1202                         $datarray['verb'] = ACTIVITY_POST;
1203                         if($wall) {
1204                                 $datarray['owner-name'] = $self[0]['name'];
1205                                 $datarray['owner-link'] = $self[0]['url'];
1206                                 $datarray['owner-avatar'] = $self[0]['thumb'];
1207                         }
1208                         if(isset($entry->application) && isset($entry->application->name) && strlen($entry->application->name))
1209                                 $datarray['app'] = strip_tags($entry->application->name);
1210                         else
1211                                 $datarray['app'] = 'facebook';
1212
1213                         $found_blocked = false;
1214
1215                         if(count($blocked_apps_arr)) {
1216                                 foreach($blocked_apps_arr as $bad_appl) {
1217                                         if(strlen(trim($bad_appl)) && (stristr($datarray['app'],trim($bad_appl)))) {
1218                                                 $found_blocked = true;
1219                                         }
1220                                 }
1221                         }
1222                                 
1223                         if($found_blocked) {
1224                                 logger('facebook: blocking application: ' . $datarray['app']);
1225                                 continue;
1226                         }
1227
1228                         $datarray['author-name'] = $from->name;
1229                         $datarray['author-link'] = 'http://facebook.com/profile.php?id=' . $from->id;
1230                         $datarray['author-avatar'] = 'https://graph.facebook.com/' . $from->id . '/picture';
1231                         $datarray['plink'] = $datarray['author-link'] . '&v=wall&story_fbid=' . substr($entry->id,strpos($entry->id,'_') + 1);
1232
1233                         $datarray['body'] = escape_tags($entry->message);
1234
1235                         if($entry->picture && $entry->link) {
1236                                 $datarray['body'] .= "\n\n" . '[url=' . $entry->link . '][img]' . $entry->picture . '[/img][/url]';
1237                         }
1238                         else {
1239                                 if($entry->picture)
1240                                         $datarray['body'] .= "\n\n" . '[img]' . $entry->picture . '[/img]';
1241                                 // if just a link, it may be a wall photo - check
1242                                 if($entry->link)
1243                                         $datarray['body'] .= fb_get_photo($uid,$entry->link);
1244                         }
1245                         if($entry->name)
1246                                 $datarray['body'] .= "\n" . $entry->name;
1247                         if($entry->caption)
1248                                 $datarray['body'] .= "\n" . $entry->caption;
1249                         if($entry->description)
1250                                 $datarray['body'] .= "\n" . $entry->description;
1251                         $datarray['created'] = datetime_convert('UTC','UTC',$entry->created_time);
1252                         $datarray['edited'] = datetime_convert('UTC','UTC',$entry->updated_time);
1253
1254                         // If the entry has a privacy policy, we cannot assume who can or cannot see it,
1255                         // as the identities are from a foreign system. Mark it as private to the owner.
1256
1257                         if($entry->privacy && $entry->privacy->value !== 'EVERYONE') {
1258                                 $datarray['private'] = 1;
1259                                 $datarray['allow_cid'] = '<' . $self[0]['id'] . '>';
1260                         }
1261
1262                         if(trim($datarray['body']) == '') {
1263                                 logger('facebook: empty body');
1264                                 continue;
1265                         }
1266
1267                         $top_item = item_store($datarray);
1268                         $r = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1269                                 intval($top_item),
1270                                 intval($uid)
1271                         );
1272                         if(count($r)) {
1273                                 $orig_post = $r[0];
1274                                 logger('fb: new top level item posted');
1275                         }
1276                 }
1277
1278                 if(isset($entry->likes) && isset($entry->likes->data))
1279                         $likers = $entry->likes->data;
1280                 else
1281                         $likers = null;
1282
1283                 if(isset($entry->comments) && isset($entry->comments->data))
1284                         $comments = $entry->comments->data;
1285                 else
1286                         $comments = null;
1287
1288                 if(is_array($likers)) {
1289                         foreach($likers as $likes) {
1290
1291                                 if(! $orig_post)
1292                                         continue;
1293
1294                                 // If we posted the like locally, it will be found with our url, not the FB url.
1295
1296                                 $second_url = (($likes->id == $self_id) ? $self[0]['url'] : 'http://facebook.com/profile.php?id=' . $likes->id); 
1297
1298                                 $r = q("SELECT * FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `verb` = '%s' 
1299                                         AND ( `author-link` = '%s' OR `author-link` = '%s' ) LIMIT 1",
1300                                         dbesc($orig_post['uri']),
1301                                         intval($uid),
1302                                         dbesc(ACTIVITY_LIKE),
1303                                         dbesc('http://facebook.com/profile.php?id=' . $likes->id),
1304                                         dbesc($second_url)
1305                                 );
1306
1307                                 if(count($r))
1308                                         continue;
1309                                         
1310                                 $likedata = array();
1311                                 $likedata['parent'] = $top_item;
1312                                 $likedata['verb'] = ACTIVITY_LIKE;
1313                                 $likedata['gravity'] = 3;
1314                                 $likedata['uid'] = $uid;
1315                                 $likedata['wall'] = (($wall) ? 1 : 0);
1316                                 $likedata['uri'] = item_new_uri($a->get_baseurl(), $uid);
1317                                 $likedata['parent-uri'] = $orig_post['uri'];
1318                                 if($likes->id == $self_id)
1319                                         $likedata['contact-id'] = $self[0]['id'];
1320                                 else {
1321                                         $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d AND `blocked` = 0 AND `readonly` = 0 LIMIT 1",
1322                                                 dbesc($likes->id),
1323                                                 intval($uid)
1324                                         );
1325                                         if(count($r))
1326                                                 $likedata['contact-id'] = $r[0]['id'];
1327                                 }
1328                                 if(! x($likedata,'contact-id'))
1329                                         $likedata['contact-id'] = $orig_post['contact-id'];
1330
1331                                 $likedata['app'] = 'facebook';
1332                                 $likedata['verb'] = ACTIVITY_LIKE;                                              
1333                                 $likedata['author-name'] = $likes->name;
1334                                 $likedata['author-link'] = 'http://facebook.com/profile.php?id=' . $likes->id;
1335                                 $likedata['author-avatar'] = 'https://graph.facebook.com/' . $likes->id . '/picture';
1336                                 
1337                                 $author  = '[url=' . $likedata['author-link'] . ']' . $likedata['author-name'] . '[/url]';
1338                                 $objauthor =  '[url=' . $orig_post['author-link'] . ']' . $orig_post['author-name'] . '[/url]';
1339                                 $post_type = t('status');
1340                         $plink = '[url=' . $orig_post['plink'] . ']' . $post_type . '[/url]';
1341                                 $likedata['object-type'] = ACTIVITY_OBJ_NOTE;
1342
1343                                 $likedata['body'] = sprintf( t('%1$s likes %2$s\'s %3$s'), $author, $objauthor, $plink);
1344                                 $likedata['object'] = '<object><type>' . ACTIVITY_OBJ_NOTE . '</type><local>1</local>' . 
1345                                         '<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>';  
1346
1347                                 $item = item_store($likedata);                  
1348                         }
1349                 }
1350                 if(is_array($comments)) {
1351                         foreach($comments as $cmnt) {
1352
1353                                 if(! $orig_post)
1354                                         continue;
1355
1356                                 $r = q("SELECT * FROM `item` WHERE `uid` = %d AND ( `uri` = '%s' OR `extid` = '%s' ) LIMIT 1",
1357                                         intval($uid),
1358                                         dbesc('fb::' . $cmnt->id),
1359                                         dbesc('fb::' . $cmnt->id)
1360                                 );
1361                                 if(count($r))
1362                                         continue;
1363
1364                                 $cmntdata = array();
1365                                 $cmntdata['parent'] = $top_item;
1366                                 $cmntdata['verb'] = ACTIVITY_POST;
1367                                 $cmntdata['gravity'] = 6;
1368                                 $cmntdata['uid'] = $uid;
1369                                 $cmntdata['wall'] = (($wall) ? 1 : 0);
1370                                 $cmntdata['uri'] = 'fb::' . $cmnt->id;
1371                                 $cmntdata['parent-uri'] = $orig_post['uri'];
1372                                 if($cmnt->from->id == $self_id) {
1373                                         $cmntdata['contact-id'] = $self[0]['id'];
1374                                 }
1375                                 else {
1376                                         $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d LIMIT 1",
1377                                                 dbesc($cmnt->from->id),
1378                                                 intval($uid)
1379                                         );
1380                                         if(count($r)) {
1381                                                 $cmntdata['contact-id'] = $r[0]['id'];
1382                                                 if($r[0]['blocked'] || $r[0]['readonly'])
1383                                                         continue;
1384                                         }
1385                                 }
1386                                 if(! x($cmntdata,'contact-id'))
1387                                         $cmntdata['contact-id'] = $orig_post['contact-id'];
1388
1389                                 $cmntdata['app'] = 'facebook';
1390                                 $cmntdata['created'] = datetime_convert('UTC','UTC',$cmnt->created_time);
1391                                 $cmntdata['edited']  = datetime_convert('UTC','UTC',$cmnt->created_time);
1392                                 $cmntdata['verb'] = ACTIVITY_POST;                                              
1393                                 $cmntdata['author-name'] = $cmnt->from->name;
1394                                 $cmntdata['author-link'] = 'http://facebook.com/profile.php?id=' . $cmnt->from->id;
1395                                 $cmntdata['author-avatar'] = 'https://graph.facebook.com/' . $cmnt->from->id . '/picture';
1396                                 $cmntdata['body'] = $cmnt->message;
1397                                 $item = item_store($cmntdata);                  
1398                                 
1399                                 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 ",
1400                                         dbesc($orig_post['uri']),
1401                                         intval($uid)
1402                                 );
1403
1404                                 if(count($myconv)) {
1405                                         $importer_url = $a->get_baseurl() . '/profile/' . $user[0]['nickname'];
1406
1407                                         foreach($myconv as $conv) {
1408
1409                                                 // now if we find a match, it means we're in this conversation
1410         
1411                                                 if(! link_compare($conv['author-link'],$importer_url))
1412                                                         continue;
1413
1414                                                 require_once('include/enotify.php');
1415                                                                 
1416                                                 $conv_parent = $conv['parent'];
1417
1418                                                 notification(array(
1419                                                         'type'         => NOTIFY_COMMENT,
1420                                                         'notify_flags' => $user[0]['notify-flags'],
1421                                                         'language'     => $user[0]['language'],
1422                                                         'to_name'      => $user[0]['username'],
1423                                                         'to_email'     => $user[0]['email'],
1424                                                         'uid'          => $user[0]['uid'],
1425                                                         'item'         => $cmntdata,
1426                                                         'link'             => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $item,
1427                                                         'source_name'  => $cmntdata['author-name'],
1428                                                         'source_link'  => $cmntdata['author-link'],
1429                                                         'source_photo' => $cmntdata['author-avatar'],
1430                                                         'verb'         => ACTIVITY_POST,
1431                                                         'otype'        => 'item',
1432                                                         'parent'       => $conv_parent,
1433                                                 ));
1434
1435                                                 // only send one notification
1436                                                 break;
1437                                         }
1438                                 }
1439                         }
1440                 }
1441         }
1442 }
1443
1444
1445 function fb_get_app_access_token() {
1446         
1447         $acc_token = get_config('facebook','app_access_token');
1448         
1449         if ($acc_token !== false) return $acc_token;
1450         
1451         $appid = get_config('facebook','appid');
1452         $appsecret = get_config('facebook', 'appsecret');
1453         
1454         if ($appid === false || $appsecret === false) {
1455                 logger('fb_get_app_access_token: appid and/or appsecret not set', LOGGER_DEBUG);
1456                 return false;
1457         }
1458         
1459         $x = fetch_url('https://graph.facebook.com/oauth/access_token?client_id=' . $appid . '&client_secret=' . $appsecret . "&grant_type=client_credentials");
1460         
1461         if(strpos($x,'access_token=') !== false) {
1462                 logger('fb_get_app_access_token: returned access token: ' . $x, LOGGER_DATA);
1463         
1464                 $token = str_replace('access_token=', '', $x);
1465                 if(strpos($token,'&') !== false)
1466                         $token = substr($token,0,strpos($token,'&'));
1467                 
1468                 if ($token == "") {
1469                         logger('fb_get_app_access_token: empty token: ' . $x, LOGGER_DEBUG);
1470                         return false;
1471                 }
1472                 set_config('facebook','app_access_token',$token);
1473                 return $token;
1474         } else {
1475                 logger('fb_get_app_access_token: response did not contain an access_token: ' . $x, LOGGER_DATA);
1476                 return false;
1477         }
1478 }
1479
1480 function facebook_subscription_del_users() {
1481         $a = get_app();
1482         $access_token = fb_get_app_access_token();
1483         
1484         $url = "https://graph.facebook.com/" . get_config('facebook', 'appid'  ) . "/subscriptions?access_token=" . $access_token;
1485         facebook_delete_url($url);
1486         
1487         del_config('facebook', 'realtime_active');
1488 }
1489
1490 function facebook_subscription_add_users() {
1491         
1492         $a = get_app();
1493         $access_token = fb_get_app_access_token();
1494         
1495         $url = "https://graph.facebook.com/" . get_config('facebook', 'appid'  ) . "/subscriptions?access_token=" . $access_token;
1496         
1497         list($usec, $sec) = explode(" ", microtime());
1498         $verify_token = sha1($usec . $sec . rand(0, 999999999));
1499         set_config('facebook', 'cb_verify_token', $verify_token);
1500         
1501         $cb = $a->get_baseurl() . '/facebook/?realtime_cb=1';
1502         
1503         $j = post_url($url,array(
1504                 "object" => "user",
1505                 "fields" => "feed,friends",
1506                 "callback_url" => $cb,
1507                 "verify_token" => $verify_token,
1508         ));
1509         del_config('facebook', 'cb_verify_token');
1510         
1511         if ($j) {
1512                 logger("Facebook reponse: " . $j, LOGGER_DATA);
1513                 
1514                 if (facebook_check_realtime_active()) set_config('facebook', 'realtime_active', 1);
1515         };
1516 }
1517
1518 function facebook_subscriptions_get() {
1519         
1520         $access_token = fb_get_app_access_token();
1521         if (!$access_token) return null;
1522         
1523         $url = "https://graph.facebook.com/" . get_config('facebook', 'appid'  ) . "/subscriptions?access_token=" . $access_token;
1524         $j = fetch_url($url);
1525         $ret = null;
1526         if ($j) {
1527                 $x = json_decode($j);
1528                 if (isset($x->data)) $ret = $x->data;
1529         }
1530         return $ret;
1531 }
1532
1533
1534 function facebook_check_realtime_active() {
1535         $ret = facebook_subscriptions_get();
1536         if (is_null($ret)) return false;
1537         if (is_array($ret)) foreach ($ret as $re) if (is_object($re) && $re->object == "user") return true;
1538         return false;
1539 }
1540
1541
1542
1543
1544 // DELETE-request to $url
1545
1546 if(! function_exists('facebook_delete_url')) {
1547 function facebook_delete_url($url,$headers = null, &$redirects = 0, $timeout = 0) {
1548         $a = get_app();
1549         $ch = curl_init($url);
1550         if(($redirects > 8) || (! $ch)) 
1551                 return false;
1552
1553         curl_setopt($ch, CURLOPT_HEADER, true);
1554         curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
1555         curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
1556         curl_setopt($ch, CURLOPT_USERAGENT, "Friendica");
1557
1558         if(intval($timeout)) {
1559                 curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
1560         }
1561         else {
1562                 $curl_time = intval(get_config('system','curl_timeout'));
1563                 curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
1564         }
1565
1566         if(defined('LIGHTTPD')) {
1567                 if(!is_array($headers)) {
1568                         $headers = array('Expect:');
1569                 } else {
1570                         if(!in_array('Expect:', $headers)) {
1571                                 array_push($headers, 'Expect:');
1572                         }
1573                 }
1574         }
1575         if($headers)
1576                 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
1577
1578         $check_cert = get_config('system','verifyssl');
1579         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
1580         $prx = get_config('system','proxy');
1581         if(strlen($prx)) {
1582                 curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
1583                 curl_setopt($ch, CURLOPT_PROXY, $prx);
1584                 $prxusr = get_config('system','proxyuser');
1585                 if(strlen($prxusr))
1586                         curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
1587         }
1588
1589         $a->set_curl_code(0);
1590
1591         // don't let curl abort the entire application
1592         // if it throws any errors.
1593
1594         $s = @curl_exec($ch);
1595
1596         $base = $s;
1597         $curl_info = curl_getinfo($ch);
1598         $http_code = $curl_info['http_code'];
1599
1600         $header = '';
1601
1602         // Pull out multiple headers, e.g. proxy and continuation headers
1603         // allow for HTTP/2.x without fixing code
1604
1605         while(preg_match('/^HTTP\/[1-2].+? [1-5][0-9][0-9]/',$base)) {
1606                 $chunk = substr($base,0,strpos($base,"\r\n\r\n")+4);
1607                 $header .= $chunk;
1608                 $base = substr($base,strlen($chunk));
1609         }
1610
1611         if($http_code == 301 || $http_code == 302 || $http_code == 303) {
1612         $matches = array();
1613         preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
1614         $url = trim(array_pop($matches));
1615         $url_parsed = @parse_url($url);
1616         if (isset($url_parsed)) {
1617             $redirects++;
1618             return delete_url($url,$headers,$redirects,$timeout);
1619         }
1620     }
1621         $a->set_curl_code($http_code);
1622         $body = substr($s,strlen($header));
1623
1624         $a->set_curl_headers($header);
1625
1626         curl_close($ch);
1627         return($body);
1628 }}