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