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