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