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