]> git.mxchange.org Git - friendica-addons.git/blob - facebook/facebook.php
Merge branch 'master' of git://github.com/friendica/friendica-addons
[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. Visit the Facebook Settings section of the "Settings->Plugin Settings" page.
29  *    and click 'Install Facebook Connector'.
30  * 3. Visit the Facebook Settings section of the "Settings->Plugin Settings" page.
31  *    and click 'Install Facebook Connector'.
32  * 4. This will ask you to login to Facebook and grant permission to the 
33  *    plugin to do its stuff. Allow it to do so. 
34  * 5. Optional step: If you want to use Facebook Real Time Updates (so new messages
35  *    and new contacts are added ~1min after they are postet / added on FB), go to
36  *    Settings -> plugins -> facebook and press the "Activate Real-Time Updates"-button.
37  * 6. You're done. To turn it off visit the Plugin Settings page again and
38  *    'Remove Facebook posting'.
39  *
40  * Vidoes and embeds will not be posted if there is no other content. Links 
41  * and images will be converted to a format suitable for the Facebook API and 
42  * long posts truncated - with a link to view the full post. 
43  *
44  * Facebook contacts will not be able to view private photos, as they are not able to
45  * authenticate to your site to establish identity. We will address this 
46  * in a future release.
47  */
48  
49  /** TODO
50  * - Implement a method for the administrator to delete all configuration data the plugin has created,
51  *   e.g. the app_access_token
52  */
53
54 define('FACEBOOK_MAXPOSTLEN', 420);
55 define('FACEBOOK_SESSION_ERR_NOTIFICATION_INTERVAL', 259200); // 3 days
56 define('FACEBOOK_DEFAULT_POLL_INTERVAL', 60); // given in minutes
57 define('FACEBOOK_MIN_POLL_INTERVAL', 5);
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         $o = '';
467         
468         $fb_installed = false;
469         if (get_pconfig(local_user(),'facebook','post')) {
470                 $access_token = get_pconfig(local_user(),'facebook','access_token');
471                 if ($access_token) {
472                         $private_wall = intval(get_pconfig($uid,'facebook','private_wall'));
473                         $s = fetch_url('https://graph.facebook.com/me/feed?access_token=' . $access_token);
474                         if($s) {
475                                 $j = json_decode($s);
476                                 if (isset($j->data)) $fb_installed = true;
477                         }
478                 }
479         }
480         
481         $appid = get_config('facebook','appid');
482
483         if(! $appid) {
484                 notice( t('Facebook API key is missing.') . EOL);
485                 return '';
486         }
487
488         $a->page['htmlhead'] .= '<link rel="stylesheet" type="text/css" href="' 
489                 . $a->get_baseurl() . '/addon/facebook/facebook.css' . '" media="all" />' . "\r\n";
490
491         $o .= '<h3>' . t('Facebook Connect') . '</h3>';
492
493         if(! $fb_installed) { 
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('Install Facebook connector for this account.') . '</a>';
498                 $o .= '</div>';
499         }
500
501         if($fb_installed) {
502                 $o .= '<div id="facebook-disable-wrapper">';
503
504                 $o .= '<a href="' . $a->get_baseurl() . '/facebook/remove' . '">' . t('Remove Facebook connector') . '</a></div>';
505
506                 $o .= '<div id="facebook-enable-wrapper">';
507
508                 $o .= '<a href="https://www.facebook.com/dialog/oauth?client_id=' . $appid . '&redirect_uri=' 
509                         . $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>';
510                 $o .= '</div>';
511         
512                 $o .= '<div id="facebook-post-default-form">';
513                 $o .= '<form action="facebook" method="post" >';
514                 $post_by_default = get_pconfig(local_user(),'facebook','post_by_default');
515                 $checked = (($post_by_default) ? ' checked="checked" ' : '');
516                 $o .= '<input type="checkbox" name="post_by_default" value="1"' . $checked . '/>' . ' ' . t('Post to Facebook by default') . EOL;
517
518                 $no_linking = get_pconfig(local_user(),'facebook','no_linking');
519                 $checked = (($no_linking) ? '' : ' checked="checked" ');
520                 $o .= '<input type="checkbox" name="facebook_linking" value="1"' . $checked . '/>' . ' ' . t('Link all your Facebook friends and conversations on this website') . EOL ;
521
522                 $o .= '<p>' . t('Facebook conversations consist of your <em>profile wall</em> and your friend <em>stream</em>.');
523                 $o .= ' ' . t('On this website, your Facebook friend stream is only visible to you.');
524                 $o .= ' ' . t('The following settings determine the privacy of your Facebook profile wall on this website.') . '</p>';
525
526                 $private_wall = get_pconfig(local_user(),'facebook','private_wall');
527                 $checked = (($private_wall) ? ' checked="checked" ' : '');
528                 $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 ;
529
530
531                 $no_wall = get_pconfig(local_user(),'facebook','no_wall');
532                 $checked = (($no_wall) ? ' checked="checked" ' : '');
533                 $o .= '<input type="checkbox" name="facebook_no_wall" value="1"' . $checked . '/>' . ' ' . t('Do not import your Facebook profile wall conversations') . EOL ;
534
535                 $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>';
536
537
538                 $blocked_apps = get_pconfig(local_user(),'facebook','blocked_apps');
539
540                 $o .= '<div><label id="blocked-apps-label" for="blocked-apps">' . t('Comma separated applications to ignore') . ' </label></div>';
541         $o .= '<div><textarea id="blocked-apps" name="blocked_apps" >' . htmlspecialchars($blocked_apps) . '</textarea></div>';
542
543                 $o .= '<input type="submit" name="submit" value="' . t('Submit') . '" /></form></div>';
544         }
545
546         return $o;
547 }
548
549
550
551 function facebook_cron($a,$b) {
552
553         $last = get_config('facebook','last_poll');
554         
555         $poll_interval = intval(get_config('facebook','poll_interval'));
556         if(! $poll_interval)
557                 $poll_interval = FACEBOOK_DEFAULT_POLL_INTERVAL;
558
559         if($last) {
560                 $next = $last + $poll_interval;
561                 if($next > time()) 
562                         return;
563         }
564
565         logger('facebook_cron');
566
567
568         // Find the FB users on this site and randomize in case one of them
569         // uses an obscene amount of memory. It may kill this queue run
570         // but hopefully we'll get a few others through on each run. 
571
572         $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'facebook' AND `k` = 'post' AND `v` = '1' ORDER BY RAND() ");
573         if(count($r)) {
574                 foreach($r as $rr) {
575                         if(get_pconfig($rr['uid'],'facebook','no_linking'))
576                                 continue;
577                         $ab = intval(get_config('system','account_abandon_days'));
578                         if($ab > 0) {
579                                 $z = q("SELECT `uid` FROM `user` WHERE `uid` = %d AND `login_date` > UTC_TIMESTAMP() - INTERVAL %d DAY LIMIT 1",
580                                         intval($rr['uid']),
581                                         intval($ab)
582                                 );
583                                 if(! count($z))
584                                         continue;
585                         }
586
587                         // check for new friends once a day
588                         $last_friend_check = get_pconfig($rr['uid'],'facebook','friend_check');
589                         if($last_friend_check) 
590                                 $next_friend_check = $last_friend_check + 86400;
591                         if($next_friend_check <= time()) {
592                                 fb_get_friends($rr['uid'], true);
593                                 set_pconfig($rr['uid'],'facebook','friend_check',time());
594                         }
595                         fb_consume_all($rr['uid']);
596                 }
597         }
598         
599         if (get_config('facebook', 'realtime_active') == 1) {
600                 if (!facebook_check_realtime_active()) {
601                         
602                         logger('facebook_cron: Facebook is not sending Real-Time Updates any more, although it is supposed to. Trying to fix it...', LOGGER_NORMAL);
603                         facebook_subscription_add_users();
604                         
605                         if (facebook_check_realtime_active()) 
606                                 logger('facebook_cron: Successful', LOGGER_NORMAL);
607                         else {
608                                 logger('facebook_cron: Failed', LOGGER_NORMAL);
609                                 
610                                 if(strlen($a->config['admin_email']) && !get_config('facebook', 'realtime_err_mailsent')) {
611                                         $res = mail($a->config['admin_email'], t('Problems with Facebook Real-Time Updates'), 
612                                                 "Hi!\n\nThere's a problem with the Facebook Real-Time Updates that cannot be solved automatically. Maybe a permission issue?\n\nPlease try to re-activate it on " . $a->config["system"]["url"] . "/admin/plugins/facebook\n\nThis e-mail will only be sent once.",
613                                                 'From: ' . t('Administrator') . '@' . $_SERVER['SERVER_NAME'] . "\n"
614                                                 . 'Content-type: text/plain; charset=UTF-8' . "\n"
615                                                 . 'Content-transfer-encoding: 8bit'
616                                         );
617                                         
618                                         set_config('facebook', 'realtime_err_mailsent', 1);
619                                 }
620                         }
621                 } else { // !facebook_check_realtime_active()
622                         del_config('facebook', 'realtime_err_mailsent');
623                 }
624         }
625         
626         set_config('facebook','last_poll', time());
627
628 }
629
630
631
632 function facebook_plugin_settings(&$a,&$b) {
633
634         $b .= '<div class="settings-block">';
635         $b .= '<h3>' . t('Facebook') . '</h3>';
636         $b .= '<a href="facebook">' . t('Facebook Connector Settings') . '</a><br />';
637         $b .= '</div>';
638
639 }
640
641
642 function facebook_plugin_admin(&$a, &$o){
643         $o = '<input type="hidden" name="form_security_token" value="' . get_form_security_token("fbsave") . '">';
644         
645         $o .= '<h4>' . t('Facebook API Key') . '</h4>';
646         
647         $appid  = get_config('facebook', 'appid'  );
648         $appsecret = get_config('facebook', 'appsecret' );
649         $poll_interval = get_config('facebook', 'poll_interval' );
650         if (!$poll_interval) $poll_interval = FACEBOOK_DEFAULT_POLL_INTERVAL;
651         
652         $working_connection = false;
653         if ($appid && $appsecret) {
654                 $subs = facebook_subscriptions_get();
655                 if ($subs === null) $o .= t('Error: the given API Key seems to be incorrect (the application access token could not be retrieved).') . '<br>';
656                 elseif (is_array($subs)) {
657                         $o .= t('The given API Key seems to work correctly.') . '<br>';
658                         $working_connection = true;
659                 } else $o .= t('The correctness of the API Key could not be detected. Somthing strang\'s going on.') . '<br>';
660         }
661         
662         $o .= '<label for="fb_appid">' . t('App-ID / API-Key') . '</label><input name="appid" type="text" value="' . escape_tags($appid ? $appid : "") . '"><br style="clear: both;">';
663         $o .= '<label for="fb_appsecret">' . t('Application secret') . '</label><input name="appsecret" type="text" value="' . escape_tags($appsecret ? $appsecret : "") . '"><br style="clear: both;">';
664         $o .= '<label for="fb_poll_interval">' . sprintf(t('Polling Interval (min. %1$s minutes)'), FACEBOOK_MIN_POLL_INTERVAL) . '</label><input name="poll_interval" type="number" min="' . FACEBOOK_MIN_POLL_INTERVAL . '" value="' . $poll_interval . '"><br style="clear: both;">';
665         $o .= '<input type="submit" name="fb_save_keys" value="' . t('Save') . '">';
666         
667         if ($working_connection) {
668                 $o .= '<h4>' . t('Real-Time Updates') . '</h4>';
669                 
670                 $activated = facebook_check_realtime_active();
671                 if ($activated) {
672                         $o .= t('Real-Time Updates are activated.') . '<br><br>';
673                         $o .= '<input type="submit" name="real_time_deactivate" value="' . t('Deactivate Real-Time Updates') . '">';
674                 } else {
675                         $o .= t('Real-Time Updates not activated.') . '<br><input type="submit" name="real_time_activate" value="' . t('Activate Real-Time Updates') . '">';
676                 }
677         }
678 }
679
680 function facebook_plugin_admin_post(&$a, &$o){
681         check_form_security_token_redirectOnErr('/admin/plugins/facebook', 'fbsave');
682         
683         if (x($_REQUEST,'fb_save_keys')) {
684                 set_config('facebook', 'appid', $_REQUEST['appid']);
685                 set_config('facebook', 'appsecret', $_REQUEST['appsecret']);
686                 $poll_interval = IntVal($_REQUEST['poll_interval']);
687                 if ($poll_interval >= FACEBOOK_MIN_POLL_INTERVAL) set_config('facebook', 'poll_interval', $poll_interval);
688                 del_config('facebook', 'app_access_token');
689                 info(t('The new values have been saved.'));
690         }
691         if (x($_REQUEST,'real_time_activate')) {
692                 facebook_subscription_add_users();
693         }
694         if (x($_REQUEST,'real_time_deactivate')) {
695                 facebook_subscription_del_users();
696         }
697 }
698
699 function facebook_jot_nets(&$a,&$b) {
700         if(! local_user())
701                 return;
702
703         $fb_post = get_pconfig(local_user(),'facebook','post');
704         if(intval($fb_post) == 1) {
705                 $fb_defpost = get_pconfig(local_user(),'facebook','post_by_default');
706                 $selected = ((intval($fb_defpost) == 1) ? ' checked="checked" ' : '');
707                 $b .= '<div class="profile-jot-net"><input type="checkbox" name="facebook_enable"' . $selected . ' value="1" /> ' 
708                         . t('Post to Facebook') . '</div>';     
709         }
710 }
711
712
713 function facebook_post_hook(&$a,&$b) {
714
715
716         if($b['deleted'] || ($b['created'] !== $b['edited']))
717                 return;
718
719         /**
720          * Post to Facebook stream
721          */
722
723         require_once('include/group.php');
724         require_once('include/html2plain.php');
725
726         logger('Facebook post');
727
728         $reply = false;
729         $likes = false;
730
731         $toplevel = (($b['id'] == $b['parent']) ? true : false);
732
733
734         $linking = ((get_pconfig($b['uid'],'facebook','no_linking')) ? 0 : 1);
735
736         if((! $toplevel) && ($linking)) {
737                 $r = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
738                         intval($b['parent']),
739                         intval($b['uid'])
740                 );
741                 if(count($r) && substr($r[0]['uri'],0,4) === 'fb::')
742                         $reply = substr($r[0]['uri'],4);
743                 elseif(count($r) && substr($r[0]['extid'],0,4) === 'fb::')
744                         $reply = substr($r[0]['extid'],4);
745                 else
746                         return;
747
748                 $u = q("SELECT * FROM user where uid = %d limit 1",
749                         intval($b['uid'])
750                 );
751                 if(! count($u))
752                         return;
753
754                 // only accept comments from the item owner. Other contacts are unknown to FB.
755  
756                 if(! link_compare($b['author-link'], $a->get_baseurl() . '/profile/' . $u[0]['nickname']))
757                         return;
758                 
759
760                 logger('facebook reply id=' . $reply);
761         }
762
763         if(strstr($b['postopts'],'facebook') || ($b['private']) || ($reply)) {
764
765                 if($b['private'] && $reply === false) {
766                         $allow_people = expand_acl($b['allow_cid']);
767                         $allow_groups = expand_groups(expand_acl($b['allow_gid']));
768                         $deny_people  = expand_acl($b['deny_cid']);
769                         $deny_groups  = expand_groups(expand_acl($b['deny_gid']));
770
771                         $recipients = array_unique(array_merge($allow_people,$allow_groups));
772                         $deny = array_unique(array_merge($deny_people,$deny_groups));
773
774                         $allow_str = dbesc(implode(', ',$recipients));
775                         if($allow_str) {
776                                 $r = q("SELECT `notify` FROM `contact` WHERE `id` IN ( $allow_str ) AND `network` = 'face'"); 
777                                 $allow_arr = array();
778                                 if(count($r)) 
779                                         foreach($r as $rr)
780                                                 $allow_arr[] = $rr['notify'];
781                         }
782
783                         $deny_str = dbesc(implode(', ',$deny));
784                         if($deny_str) {
785                                 $r = q("SELECT `notify` FROM `contact` WHERE `id` IN ( $deny_str ) AND `network` = 'face'"); 
786                                 $deny_arr = array();
787                                 if(count($r)) 
788                                         foreach($r as $rr)
789                                                 $deny_arr[] = $rr['notify'];
790                         }
791
792                         if(count($deny_arr) && (! count($allow_arr))) {
793
794                                 // One or more FB folks were denied access but nobody on FB was specifically allowed access.
795                                 // This might cause the post to be open to public on Facebook, but only to selected members
796                                 // on another network. Since this could potentially leak a post to somebody who was denied, 
797                                 // we will skip posting it to Facebook with a slightly vague but relevant message that will 
798                                 // hopefully lead somebody to this code comment for a better explanation of what went wrong.
799
800                                 notice( t('Post to Facebook cancelled because of multi-network access permission conflict.') . EOL);
801                                 return;
802                         }
803
804
805                         // if it's a private message but no Facebook members are allowed or denied, skip Facebook post
806
807                         if((! count($allow_arr)) && (! count($deny_arr)))
808                                 return;
809                 }
810
811                 if($b['verb'] == ACTIVITY_LIKE)
812                         $likes = true;                          
813
814
815                 $appid  = get_config('facebook', 'appid'  );
816                 $secret = get_config('facebook', 'appsecret' );
817
818                 if($appid && $secret) {
819
820                         logger('facebook: have appid+secret');
821
822                         $fb_token  = get_pconfig($b['uid'],'facebook','access_token');
823
824
825                         // post to facebook if it's a public post and we've ticked the 'post to Facebook' box, 
826                         // or it's a private message with facebook participants
827                         // or it's a reply or likes action to an existing facebook post                 
828
829                         if($fb_token && ($toplevel || $b['private'] || $reply)) {
830                                 logger('facebook: able to post');
831                                 require_once('library/facebook.php');
832                                 require_once('include/bbcode.php');     
833
834                                 $msg = $b['body'];
835
836                                 logger('Facebook post: original msg=' . $msg, LOGGER_DATA);
837
838                                 // make links readable before we strip the code
839
840                                 // unless it's a dislike - just send the text as a comment
841
842                                 if($b['verb'] == ACTIVITY_DISLIKE)
843                                         $msg = trim(strip_tags(bbcode($msg)));
844
845                                 /*$search_str = $a->get_baseurl() . '/search';
846
847                                 if(preg_match("/\[url=(.*?)\](.*?)\[\/url\]/is",$msg,$matches)) {
848
849                                         // don't use hashtags for message link
850
851                                         if(strpos($matches[2],$search_str) === false) {
852                                                 $link = $matches[1];
853                                                 if(substr($matches[2],0,5) != '[img]')
854                                                         $linkname = $matches[2];
855                                         }
856                                 }
857
858                                 // strip tag links to avoid link clutter, this really should be 
859                                 // configurable because we're losing information
860
861                                 $msg = preg_replace("/\#\[url=(.*?)\](.*?)\[\/url\]/is",'#$2',$msg);
862
863                                 // provide the link separately for normal links
864                                 $msg = preg_replace("/\[url=(.*?)\](.*?)\[\/url\]/is",'$2 $1',$msg);
865
866                                 if(preg_match("/\[img\](.*?)\[\/img\]/is",$msg,$matches))
867                                         $image = $matches[1];
868
869                                 $msg = preg_replace("/\[img\](.*?)\[\/img\]/is", t('Image: ') . '$1', $msg);
870
871                                 if((strpos($link,z_root()) !== false) && (! $image))
872                                         $image = $a->get_baseurl() . '/images/friendica-64.jpg';
873
874                                 $msg = trim(strip_tags(bbcode($msg)));*/
875
876                                 // Test
877
878                                 // Looking for images
879                                 if(preg_match("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/is",$b['body'],$matches))
880                                         $image = $matches[3];
881
882                                 if(preg_match("/\[img\](.*?)\[\/img\]/is",$b['body'],$matches))
883                                         $image = $matches[1];
884
885                                 $html = bbcode($b['body']);
886                                 $msg = trim($b['title']." \n".html2plain($html, 0, true));
887                                 $msg = html_entity_decode($msg,ENT_QUOTES,'UTF-8');
888
889                                 $toolong = false;
890
891                                 // add any attachments as text urls
892
893                                 $arr = explode(',',$b['attach']);
894
895                                 if(count($arr)) {
896                                         $msg .= "\n";
897                                         foreach($arr as $r) {
898                                                 $matches = false;
899                                                 $cnt = preg_match('|\[attach\]href=\"(.*?)\" size=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"\[\/attach\]|',$r,$matches);
900                                                 if($cnt) {
901                                                         $msg .= "\n".$matches[1];
902                                                 }
903                                         }
904                                 }
905
906                                 // To-Do: look for bookmark-bbcode and handle it with priority
907
908                                 $links = collecturls($html);
909                                 if (sizeof($links) > 0) {
910                                         reset($links);
911                                         $link = current($links);
912                                         /*if (strlen($msg."\n".$link) <= FACEBOOK_MAXPOSTLEN)
913                                                 $msg .= "\n".$link;
914                                         else
915                                                 $toolong = true;*/
916                                 }
917
918                                 if ((strlen($msg) > FACEBOOK_MAXPOSTLEN) or $toolong) {
919                                         $shortlink = "";
920                                         require_once('library/slinky.php');
921
922                                         $display_url = $b['plink'];
923
924                                         $slinky = new Slinky( $display_url );
925                                         // setup a cascade of shortening services
926                                         // try to get a short link from these services
927                                         // in the order ur1.ca, trim, id.gd, tinyurl
928                                         $slinky->set_cascade( array( new Slinky_UR1ca(), new Slinky_Trim(), new Slinky_IsGd(), new Slinky_TinyURL() ) );
929                                         $shortlink = $slinky->short();
930                                         // the new message will be shortened such that "... $shortlink"
931                                         // will fit into the character limit
932                                         $msg = substr($msg, 0, FACEBOOK_MAXPOSTLEN - strlen($shortlink) - 4);
933                                         $msg .= '... ' . $shortlink;
934                                 }
935                                 if(! strlen($msg))
936                                         return;
937
938                                 logger('Facebook post: msg=' . $msg, LOGGER_DATA);
939
940                                 if($likes) { 
941                                         $postvars = array('access_token' => $fb_token);
942                                 }
943                                 else {
944                                         $postvars = array(
945                                                 'access_token' => $fb_token, 
946                                                 'message' => $msg
947                                         );
948                                         if(isset($image))
949                                                 $postvars['picture'] = $image;
950                                         if(isset($link))
951                                                 $postvars['link'] = $link;
952                                         if(isset($linkname))
953                                                 $postvars['name'] = $linkname;
954                                 }
955
956                                 if(($b['private']) && ($toplevel)) {
957                                         $postvars['privacy'] = '{"value": "CUSTOM", "friends": "SOME_FRIENDS"';
958                                         if(count($allow_arr))
959                                                 $postvars['privacy'] .= ',"allow": "' . implode(',',$allow_arr) . '"';
960                                         if(count($deny_arr))
961                                                 $postvars['privacy'] .= ',"deny": "' . implode(',',$deny_arr) . '"';
962                                         $postvars['privacy'] .= '}';
963
964                                 }
965
966                                 if($reply) {
967                                         $url = 'https://graph.facebook.com/' . $reply . '/' . (($likes) ? 'likes' : 'comments');
968                                 }
969                                 else { 
970                                         $url = 'https://graph.facebook.com/me/feed';
971                                         if($b['plink'])
972                                                 $postvars['actions'] = '{"name": "' . t('View on Friendica') . '", "link": "' .  $b['plink'] . '"}';
973                                 }
974
975                                 logger('facebook: post to ' . $url);
976                                 logger('facebook: postvars: ' . print_r($postvars,true));
977
978                                 // "test_mode" prevents anything from actually being posted.
979                                 // Otherwise, let's do it.
980
981                                 if(! get_config('facebook','test_mode')) {
982                                         $x = post_url($url, $postvars);
983                                         logger('Facebook post returns: ' . $x, LOGGER_DEBUG);
984
985                                         $retj = json_decode($x);
986                                         if($retj->id) {
987                                                 q("UPDATE `item` SET `extid` = '%s' WHERE `id` = %d LIMIT 1",
988                                                         dbesc('fb::' . $retj->id),
989                                                         intval($b['id'])
990                                                 );
991                                         }
992                                         else {
993                                                 if(! $likes) {
994                                                         $s = serialize(array('url' => $url, 'item' => $b['id'], 'post' => $postvars));
995                                                         require_once('include/queue_fn.php');
996                                                         add_to_queue($a->contact,NETWORK_FACEBOOK,$s);
997                                                         notice( t('Facebook post failed. Queued for retry.') . EOL);
998                                                 }
999                                                 
1000                                                 if (isset($retj->error) && $retj->error->type == "OAuthException" && $retj->error->code == 190) {
1001                                                         logger('Facebook session has expired due to changed password.', LOGGER_DEBUG);
1002                                                         
1003                                                         $last_notification = get_pconfig($b['uid'], 'facebook', 'session_expired_mailsent');
1004                                                         if (!$last_notification || $last_notification < (time() - FACEBOOK_SESSION_ERR_NOTIFICATION_INTERVAL)) {
1005                                                                 require_once('include/enotify.php');
1006                                                         
1007                                                                 $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", intval($b['uid']) );
1008                                                                 notification(array(
1009                                                                         'uid' => $b['uid'],
1010                                                                         'type' => NOTIFY_SYSTEM,
1011                                                                         'system_type' => 'facebook_connection_invalid',
1012                                                                         'language'     => $r[0]['language'],
1013                                                                         'to_name'      => $r[0]['username'],
1014                                                                         'to_email'     => $r[0]['email'],
1015                                                                         'source_name'  => t('Administrator'),
1016                                                                         'source_link'  => $a->config["system"]["url"],
1017                                                                         'source_photo' => $a->config["system"]["url"] . '/images/person-80.jpg',
1018                                                                 ));
1019                                                                 
1020                                                                 set_pconfig($b['uid'], 'facebook', 'session_expired_mailsent', time());
1021                                                         } else logger('Facebook: No notification, as the last one was sent on ' . $last_notification, LOGGER_DEBUG);
1022                                                 }
1023                                         }
1024                                 }
1025                         }
1026                 }
1027         }
1028 }
1029
1030 function facebook_enotify(&$app, &$data) {
1031         if (x($data, 'params') && $data['params']['type'] == NOTIFY_SYSTEM && x($data['params'], 'system_type') && $data['params']['system_type'] == 'facebook_connection_invalid') {
1032                 $data['itemlink'] = '/facebook';
1033                 $data['epreamble'] = $data['preamble'] = t('Your Facebook connection became invalid. Please Re-authenticate.');
1034                 $data['subject'] = t('Facebook connection became invalid');
1035                 $data['body'] = sprintf( t("Hi %1\$s,\n\nThe connection between your accounts on %2\$s and Facebook became invalid. This usually happens after you change your Facebook-password. To enable the connection again, you have to %3\$sre-authenticate the Facebook-connector%4\$s."), $data['params']['to_name'], "[url=" . $app->config["system"]["url"] . "]" . $app->config["sitename"] . "[/url]", "[url=" . $app->config["system"]["url"] . "/facebook]", "[/url]");
1036         }
1037 }
1038
1039 function facebook_post_local(&$a,&$b) {
1040
1041         // Figure out if Facebook posting is enabled for this post and file it in 'postopts'
1042         // where we will discover it during background delivery.
1043
1044         // This can only be triggered by a local user posting to their own wall.
1045
1046         if((local_user()) && (local_user() == $b['uid'])) {
1047
1048                 $fb_post   = intval(get_pconfig(local_user(),'facebook','post'));
1049                 $fb_enable = (($fb_post && x($_REQUEST,'facebook_enable')) ? intval($_REQUEST['facebook_enable']) : 0);
1050
1051                 // if API is used, default to the chosen settings
1052                 if($_REQUEST['api_source'] && intval(get_pconfig(local_user(),'facebook','post_by_default')))
1053                         $fb_enable = 1;
1054
1055                 if(! $fb_enable)
1056                         return;
1057
1058                 if(strlen($b['postopts']))
1059                         $b['postopts'] .= ',';
1060                 $b['postopts'] .= 'facebook';
1061         }
1062 }
1063
1064
1065 function fb_queue_hook(&$a,&$b) {
1066
1067         $qi = q("SELECT * FROM `queue` WHERE `network` = '%s'",
1068                 dbesc(NETWORK_FACEBOOK)
1069         );
1070         if(! count($qi))
1071                 return;
1072
1073         require_once('include/queue_fn.php');
1074
1075         foreach($qi as $x) {
1076                 if($x['network'] !== NETWORK_FACEBOOK)
1077                         continue;
1078
1079                 logger('facebook_queue: run');
1080
1081                 $r = q("SELECT `user`.* FROM `user` LEFT JOIN `contact` on `contact`.`uid` = `user`.`uid` 
1082                         WHERE `contact`.`self` = 1 AND `contact`.`id` = %d LIMIT 1",
1083                         intval($x['cid'])
1084                 );
1085                 if(! count($r))
1086                         continue;
1087
1088                 $user = $r[0];
1089
1090                 $appid  = get_config('facebook', 'appid'  );
1091                 $secret = get_config('facebook', 'appsecret' );
1092
1093                 if($appid && $secret) {
1094                         $fb_post   = intval(get_pconfig($user['uid'],'facebook','post'));
1095                         $fb_token  = get_pconfig($user['uid'],'facebook','access_token');
1096
1097                         if($fb_post && $fb_token) {
1098                                 logger('facebook_queue: able to post');
1099                                 require_once('library/facebook.php');
1100
1101                                 $z = unserialize($x['content']);
1102                                 $item = $z['item'];
1103                                 $j = post_url($z['url'],$z['post']);
1104
1105                                 $retj = json_decode($j);
1106                                 if($retj->id) {
1107                                         q("UPDATE `item` SET `extid` = '%s' WHERE `id` = %d LIMIT 1",
1108                                                 dbesc('fb::' . $retj->id),
1109                                                 intval($item)
1110                                         );
1111                                         logger('facebook_queue: success: ' . $j); 
1112                                         remove_queue_item($x['id']);
1113                                 }
1114                                 else {
1115                                         logger('facebook_queue: failed: ' . $j);
1116                                         update_queue_time($x['id']);
1117                                 }
1118                         }
1119                 }
1120         }
1121 }
1122
1123 function fb_consume_all($uid) {
1124
1125         require_once('include/items.php');
1126
1127         $access_token = get_pconfig($uid,'facebook','access_token');
1128         if(! $access_token)
1129                 return;
1130         
1131         if(! get_pconfig($uid,'facebook','no_wall')) {
1132                 $private_wall = intval(get_pconfig($uid,'facebook','private_wall'));
1133                 $s = fetch_url('https://graph.facebook.com/me/feed?access_token=' . $access_token);
1134                 if($s) {
1135                         $j = json_decode($s);
1136                         if (isset($j->data)) {
1137                                 logger('fb_consume_stream: wall: ' . print_r($j,true), LOGGER_DATA);
1138                                 fb_consume_stream($uid,$j,($private_wall) ? false : true);
1139                         } else {
1140                                 logger('fb_consume_stream: wall: got no data from Facebook: ' . print_r($j,true), LOGGER_NORMAL);
1141                         }
1142                 }
1143         }
1144         $s = fetch_url('https://graph.facebook.com/me/home?access_token=' . $access_token);
1145         if($s) {
1146                 $j = json_decode($s);
1147                 if (isset($j->data)) {
1148                         logger('fb_consume_stream: feed: ' . print_r($j,true), LOGGER_DATA);
1149                         fb_consume_stream($uid,$j,false);
1150                 } else {
1151                         logger('fb_consume_stream: feed: got no data from Facebook: ' . print_r($j,true), LOGGER_NORMAL);
1152                 }
1153         }
1154
1155 }
1156
1157 function fb_get_photo($uid,$link) {
1158         $access_token = get_pconfig($uid,'facebook','access_token');
1159         if(! $access_token || (! stristr($link,'facebook.com/photo.php')))
1160                 return "\n" . '[url=' . $link . ']' . t('link') . '[/url]';
1161         $ret = preg_match('/fbid=([0-9]*)/',$link,$match);
1162         if($ret)
1163                 $photo_id = $match[1];
1164         $x = fetch_url('https://graph.facebook.com/' . $photo_id . '?access_token=' . $access_token);
1165         $j = json_decode($x);
1166         if($j->picture)
1167                 return "\n\n" . '[url=' . $link . '][img]' . $j->picture . '[/img][/url]';
1168         else
1169                 return "\n" . '[url=' . $link . ']' . t('link') . '[/url]';
1170 }
1171
1172 function fb_consume_stream($uid,$j,$wall = false) {
1173
1174         $a = get_app();
1175
1176
1177         $user = q("SELECT * FROM `user` WHERE `uid` = %d AND `account_expired` = 0 LIMIT 1",
1178                 intval($uid)
1179         );
1180         if(! count($user))
1181                 return;
1182
1183         $my_local_url = $a->get_baseurl() . '/profile/' . $user[0]['nickname'];
1184
1185         $no_linking = get_pconfig($uid,'facebook','no_linking');
1186         if($no_linking)
1187                 return;
1188
1189         $self = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
1190                 intval($uid)
1191         );
1192
1193         $blocked_apps = get_pconfig($uid,'facebook','blocked_apps');
1194         $blocked_apps_arr = explode(',',$blocked_apps);
1195
1196         $self_id = get_pconfig($uid,'facebook','self_id');
1197         if(! count($j->data) || (! strlen($self_id)))
1198                 return;
1199
1200         foreach($j->data as $entry) {
1201                 logger('fb_consume: entry: ' . print_r($entry,true), LOGGER_DATA);
1202                 $datarray = array();
1203
1204                 $r = q("SELECT * FROM `item` WHERE ( `uri` = '%s' OR `extid` = '%s') AND `uid` = %d LIMIT 1",
1205                                 dbesc('fb::' . $entry->id),
1206                                 dbesc('fb::' . $entry->id),
1207                                 intval($uid)
1208                 );
1209                 if(count($r)) {
1210                         $post_exists = true;
1211                         $orig_post = $r[0];
1212                         $top_item = $r[0]['id'];
1213                 }
1214                 else {
1215                         $post_exists = false;
1216                         $orig_post = null;
1217                 }
1218
1219                 if(! $orig_post) {
1220                         $datarray['gravity'] = 0;
1221                         $datarray['uid'] = $uid;
1222                         $datarray['wall'] = (($wall) ? 1 : 0);
1223                         $datarray['uri'] = $datarray['parent-uri'] = 'fb::' . $entry->id;
1224                         $from = $entry->from;
1225                         if($from->id == $self_id)
1226                                 $datarray['contact-id'] = $self[0]['id'];
1227                         else {
1228                                 $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d AND `blocked` = 0 AND `readonly` = 0 LIMIT 1",
1229                                         dbesc($from->id),
1230                                         intval($uid)
1231                                 );
1232                                 if(count($r))
1233                                         $datarray['contact-id'] = $r[0]['id'];
1234                         }
1235
1236                         // don't store post if we don't have a contact
1237
1238                         if(! x($datarray,'contact-id')) {
1239                                 logger('no contact: post ignored');
1240                                 continue;
1241                         }
1242
1243                         $datarray['verb'] = ACTIVITY_POST;
1244                         if($wall) {
1245                                 $datarray['owner-name'] = $self[0]['name'];
1246                                 $datarray['owner-link'] = $self[0]['url'];
1247                                 $datarray['owner-avatar'] = $self[0]['thumb'];
1248                         }
1249                         if(isset($entry->application) && isset($entry->application->name) && strlen($entry->application->name))
1250                                 $datarray['app'] = strip_tags($entry->application->name);
1251                         else
1252                                 $datarray['app'] = 'facebook';
1253
1254                         $found_blocked = false;
1255
1256                         if(count($blocked_apps_arr)) {
1257                                 foreach($blocked_apps_arr as $bad_appl) {
1258                                         if(strlen(trim($bad_appl)) && (stristr($datarray['app'],trim($bad_appl)))) {
1259                                                 $found_blocked = true;
1260                                         }
1261                                 }
1262                         }
1263                                 
1264                         if($found_blocked) {
1265                                 logger('facebook: blocking application: ' . $datarray['app']);
1266                                 continue;
1267                         }
1268
1269                         $datarray['author-name'] = $from->name;
1270                         $datarray['author-link'] = 'http://facebook.com/profile.php?id=' . $from->id;
1271                         $datarray['author-avatar'] = 'https://graph.facebook.com/' . $from->id . '/picture';
1272                         $datarray['plink'] = $datarray['author-link'] . '&v=wall&story_fbid=' . substr($entry->id,strpos($entry->id,'_') + 1);
1273
1274                         $datarray['body'] = escape_tags($entry->message);
1275
1276                         if($entry->picture && $entry->link) {
1277                                 $datarray['body'] .= "\n\n" . '[url=' . $entry->link . '][img]' . $entry->picture . '[/img][/url]';
1278                         }
1279                         else {
1280                                 if($entry->picture)
1281                                         $datarray['body'] .= "\n\n" . '[img]' . $entry->picture . '[/img]';
1282                                 // if just a link, it may be a wall photo - check
1283                                 if($entry->link)
1284                                         $datarray['body'] .= fb_get_photo($uid,$entry->link);
1285                         }
1286                         if($entry->name)
1287                                 $datarray['body'] .= "\n" . $entry->name;
1288                         if($entry->caption)
1289                                 $datarray['body'] .= "\n" . $entry->caption;
1290                         if($entry->description)
1291                                 $datarray['body'] .= "\n" . $entry->description;
1292                         $datarray['created'] = datetime_convert('UTC','UTC',$entry->created_time);
1293                         $datarray['edited'] = datetime_convert('UTC','UTC',$entry->updated_time);
1294
1295                         // If the entry has a privacy policy, we cannot assume who can or cannot see it,
1296                         // as the identities are from a foreign system. Mark it as private to the owner.
1297
1298                         if($entry->privacy && $entry->privacy->value !== 'EVERYONE') {
1299                                 $datarray['private'] = 1;
1300                                 $datarray['allow_cid'] = '<' . $self[0]['id'] . '>';
1301                         }
1302
1303                         if(trim($datarray['body']) == '') {
1304                                 logger('facebook: empty body');
1305                                 continue;
1306                         }
1307
1308                         $top_item = item_store($datarray);
1309                         $r = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1310                                 intval($top_item),
1311                                 intval($uid)
1312                         );
1313                         if(count($r)) {
1314                                 $orig_post = $r[0];
1315                                 logger('fb: new top level item posted');
1316                         }
1317                 }
1318
1319                 if(isset($entry->likes) && isset($entry->likes->data))
1320                         $likers = $entry->likes->data;
1321                 else
1322                         $likers = null;
1323
1324                 if(isset($entry->comments) && isset($entry->comments->data))
1325                         $comments = $entry->comments->data;
1326                 else
1327                         $comments = null;
1328
1329                 if(is_array($likers)) {
1330                         foreach($likers as $likes) {
1331
1332                                 if(! $orig_post)
1333                                         continue;
1334
1335                                 // If we posted the like locally, it will be found with our url, not the FB url.
1336
1337                                 $second_url = (($likes->id == $self_id) ? $self[0]['url'] : 'http://facebook.com/profile.php?id=' . $likes->id); 
1338
1339                                 $r = q("SELECT * FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `verb` = '%s' 
1340                                         AND ( `author-link` = '%s' OR `author-link` = '%s' ) LIMIT 1",
1341                                         dbesc($orig_post['uri']),
1342                                         intval($uid),
1343                                         dbesc(ACTIVITY_LIKE),
1344                                         dbesc('http://facebook.com/profile.php?id=' . $likes->id),
1345                                         dbesc($second_url)
1346                                 );
1347
1348                                 if(count($r))
1349                                         continue;
1350                                         
1351                                 $likedata = array();
1352                                 $likedata['parent'] = $top_item;
1353                                 $likedata['verb'] = ACTIVITY_LIKE;
1354                                 $likedata['gravity'] = 3;
1355                                 $likedata['uid'] = $uid;
1356                                 $likedata['wall'] = (($wall) ? 1 : 0);
1357                                 $likedata['uri'] = item_new_uri($a->get_baseurl(), $uid);
1358                                 $likedata['parent-uri'] = $orig_post['uri'];
1359                                 if($likes->id == $self_id)
1360                                         $likedata['contact-id'] = $self[0]['id'];
1361                                 else {
1362                                         $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d AND `blocked` = 0 AND `readonly` = 0 LIMIT 1",
1363                                                 dbesc($likes->id),
1364                                                 intval($uid)
1365                                         );
1366                                         if(count($r))
1367                                                 $likedata['contact-id'] = $r[0]['id'];
1368                                 }
1369                                 if(! x($likedata,'contact-id'))
1370                                         $likedata['contact-id'] = $orig_post['contact-id'];
1371
1372                                 $likedata['app'] = 'facebook';
1373                                 $likedata['verb'] = ACTIVITY_LIKE;                                              
1374                                 $likedata['author-name'] = $likes->name;
1375                                 $likedata['author-link'] = 'http://facebook.com/profile.php?id=' . $likes->id;
1376                                 $likedata['author-avatar'] = 'https://graph.facebook.com/' . $likes->id . '/picture';
1377                                 
1378                                 $author  = '[url=' . $likedata['author-link'] . ']' . $likedata['author-name'] . '[/url]';
1379                                 $objauthor =  '[url=' . $orig_post['author-link'] . ']' . $orig_post['author-name'] . '[/url]';
1380                                 $post_type = t('status');
1381                         $plink = '[url=' . $orig_post['plink'] . ']' . $post_type . '[/url]';
1382                                 $likedata['object-type'] = ACTIVITY_OBJ_NOTE;
1383
1384                                 $likedata['body'] = sprintf( t('%1$s likes %2$s\'s %3$s'), $author, $objauthor, $plink);
1385                                 $likedata['object'] = '<object><type>' . ACTIVITY_OBJ_NOTE . '</type><local>1</local>' . 
1386                                         '<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>';  
1387
1388                                 $item = item_store($likedata);                  
1389                         }
1390                 }
1391                 if(is_array($comments)) {
1392                         foreach($comments as $cmnt) {
1393
1394                                 if(! $orig_post)
1395                                         continue;
1396
1397                                 $r = q("SELECT * FROM `item` WHERE `uid` = %d AND ( `uri` = '%s' OR `extid` = '%s' ) LIMIT 1",
1398                                         intval($uid),
1399                                         dbesc('fb::' . $cmnt->id),
1400                                         dbesc('fb::' . $cmnt->id)
1401                                 );
1402                                 if(count($r))
1403                                         continue;
1404
1405                                 $cmntdata = array();
1406                                 $cmntdata['parent'] = $top_item;
1407                                 $cmntdata['verb'] = ACTIVITY_POST;
1408                                 $cmntdata['gravity'] = 6;
1409                                 $cmntdata['uid'] = $uid;
1410                                 $cmntdata['wall'] = (($wall) ? 1 : 0);
1411                                 $cmntdata['uri'] = 'fb::' . $cmnt->id;
1412                                 $cmntdata['parent-uri'] = $orig_post['uri'];
1413                                 if($cmnt->from->id == $self_id) {
1414                                         $cmntdata['contact-id'] = $self[0]['id'];
1415                                 }
1416                                 else {
1417                                         $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d LIMIT 1",
1418                                                 dbesc($cmnt->from->id),
1419                                                 intval($uid)
1420                                         );
1421                                         if(count($r)) {
1422                                                 $cmntdata['contact-id'] = $r[0]['id'];
1423                                                 if($r[0]['blocked'] || $r[0]['readonly'])
1424                                                         continue;
1425                                         }
1426                                 }
1427                                 if(! x($cmntdata,'contact-id'))
1428                                         $cmntdata['contact-id'] = $orig_post['contact-id'];
1429
1430                                 $cmntdata['app'] = 'facebook';
1431                                 $cmntdata['created'] = datetime_convert('UTC','UTC',$cmnt->created_time);
1432                                 $cmntdata['edited']  = datetime_convert('UTC','UTC',$cmnt->created_time);
1433                                 $cmntdata['verb'] = ACTIVITY_POST;                                              
1434                                 $cmntdata['author-name'] = $cmnt->from->name;
1435                                 $cmntdata['author-link'] = 'http://facebook.com/profile.php?id=' . $cmnt->from->id;
1436                                 $cmntdata['author-avatar'] = 'https://graph.facebook.com/' . $cmnt->from->id . '/picture';
1437                                 $cmntdata['body'] = $cmnt->message;
1438                                 $item = item_store($cmntdata);                  
1439                                 
1440                                 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 ",
1441                                         dbesc($orig_post['uri']),
1442                                         intval($uid)
1443                                 );
1444
1445                                 if(count($myconv)) {
1446                                         $importer_url = $a->get_baseurl() . '/profile/' . $user[0]['nickname'];
1447
1448                                         foreach($myconv as $conv) {
1449
1450                                                 // now if we find a match, it means we're in this conversation
1451         
1452                                                 if(! link_compare($conv['author-link'],$importer_url))
1453                                                         continue;
1454
1455                                                 require_once('include/enotify.php');
1456                                                                 
1457                                                 $conv_parent = $conv['parent'];
1458
1459                                                 notification(array(
1460                                                         'type'         => NOTIFY_COMMENT,
1461                                                         'notify_flags' => $user[0]['notify-flags'],
1462                                                         'language'     => $user[0]['language'],
1463                                                         'to_name'      => $user[0]['username'],
1464                                                         'to_email'     => $user[0]['email'],
1465                                                         'uid'          => $user[0]['uid'],
1466                                                         'item'         => $cmntdata,
1467                                                         'link'             => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $item,
1468                                                         'source_name'  => $cmntdata['author-name'],
1469                                                         'source_link'  => $cmntdata['author-link'],
1470                                                         'source_photo' => $cmntdata['author-avatar'],
1471                                                         'verb'         => ACTIVITY_POST,
1472                                                         'otype'        => 'item',
1473                                                         'parent'       => $conv_parent,
1474                                                 ));
1475
1476                                                 // only send one notification
1477                                                 break;
1478                                         }
1479                                 }
1480                         }
1481                 }
1482         }
1483 }
1484
1485
1486 function fb_get_app_access_token() {
1487         
1488         $acc_token = get_config('facebook','app_access_token');
1489         
1490         if ($acc_token !== false) return $acc_token;
1491         
1492         $appid = get_config('facebook','appid');
1493         $appsecret = get_config('facebook', 'appsecret');
1494         
1495         if ($appid === false || $appsecret === false) {
1496                 logger('fb_get_app_access_token: appid and/or appsecret not set', LOGGER_DEBUG);
1497                 return false;
1498         }
1499         logger('https://graph.facebook.com/oauth/access_token?client_id=' . $appid . '&client_secret=' . $appsecret . '&grant_type=client_credentials', LOGGER_DATA);
1500         $x = fetch_url('https://graph.facebook.com/oauth/access_token?client_id=' . $appid . '&client_secret=' . $appsecret . '&grant_type=client_credentials');
1501         
1502         if(strpos($x,'access_token=') !== false) {
1503                 logger('fb_get_app_access_token: returned access token: ' . $x, LOGGER_DATA);
1504         
1505                 $token = str_replace('access_token=', '', $x);
1506                 if(strpos($token,'&') !== false)
1507                         $token = substr($token,0,strpos($token,'&'));
1508                 
1509                 if ($token == "") {
1510                         logger('fb_get_app_access_token: empty token: ' . $x, LOGGER_DEBUG);
1511                         return false;
1512                 }
1513                 set_config('facebook','app_access_token',$token);
1514                 return $token;
1515         } else {
1516                 logger('fb_get_app_access_token: response did not contain an access_token: ' . $x, LOGGER_DATA);
1517                 return false;
1518         }
1519 }
1520
1521 function facebook_subscription_del_users() {
1522         $a = get_app();
1523         $access_token = fb_get_app_access_token();
1524         
1525         $url = "https://graph.facebook.com/" . get_config('facebook', 'appid'  ) . "/subscriptions?access_token=" . $access_token;
1526         facebook_delete_url($url);
1527         
1528         if (!facebook_check_realtime_active()) del_config('facebook', 'realtime_active');
1529 }
1530
1531 function facebook_subscription_add_users($second_try = false) {
1532         $a = get_app();
1533         $access_token = fb_get_app_access_token();
1534         
1535         $url = "https://graph.facebook.com/" . get_config('facebook', 'appid'  ) . "/subscriptions?access_token=" . $access_token;
1536         
1537         list($usec, $sec) = explode(" ", microtime());
1538         $verify_token = sha1($usec . $sec . rand(0, 999999999));
1539         set_config('facebook', 'cb_verify_token', $verify_token);
1540         
1541         $cb = $a->get_baseurl() . '/facebook/?realtime_cb=1';
1542         
1543         $j = post_url($url,array(
1544                 "object" => "user",
1545                 "fields" => "feed,friends",
1546                 "callback_url" => $cb,
1547                 "verify_token" => $verify_token,
1548         ));
1549         del_config('facebook', 'cb_verify_token');
1550         
1551         if ($j) {
1552                 $x = json_decode($j);
1553                 logger("Facebook reponse: " . $j, LOGGER_DATA);
1554                 if (isset($x->error)) {
1555                         logger('facebook_subscription_add_users: got an error: ' . $j);
1556                         if ($x->error->type == "OAuthException" && $x->error->code == 190) {
1557                                 del_config('facebook', 'app_access_token');
1558                                 if ($second_try === false) facebook_subscription_add_users(true);
1559                         }
1560                 } else {
1561                         logger('facebook_subscription_add_users: sucessful');
1562                         if (facebook_check_realtime_active()) set_config('facebook', 'realtime_active', 1);
1563                 }
1564         };
1565 }
1566
1567 function facebook_subscriptions_get() {
1568         
1569         $access_token = fb_get_app_access_token();
1570         if (!$access_token) return null;
1571         
1572         $url = "https://graph.facebook.com/" . get_config('facebook', 'appid'  ) . "/subscriptions?access_token=" . $access_token;
1573         $j = fetch_url($url);
1574         $ret = null;
1575         if ($j) {
1576                 $x = json_decode($j);
1577                 if (isset($x->data)) $ret = $x->data;
1578         }
1579         return $ret;
1580 }
1581
1582
1583 function facebook_check_realtime_active() {
1584         $ret = facebook_subscriptions_get();
1585         if (is_null($ret)) return false;
1586         if (is_array($ret)) foreach ($ret as $re) if (is_object($re) && $re->object == "user") return true;
1587         return false;
1588 }
1589
1590
1591
1592
1593 // DELETE-request to $url
1594
1595 if(! function_exists('facebook_delete_url')) {
1596 function facebook_delete_url($url,$headers = null, &$redirects = 0, $timeout = 0) {
1597         $a = get_app();
1598         $ch = curl_init($url);
1599         if(($redirects > 8) || (! $ch)) 
1600                 return false;
1601
1602         curl_setopt($ch, CURLOPT_HEADER, true);
1603         curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
1604         curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
1605         curl_setopt($ch, CURLOPT_USERAGENT, "Friendica");
1606
1607         if(intval($timeout)) {
1608                 curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
1609         }
1610         else {
1611                 $curl_time = intval(get_config('system','curl_timeout'));
1612                 curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60));
1613         }
1614
1615         if(defined('LIGHTTPD')) {
1616                 if(!is_array($headers)) {
1617                         $headers = array('Expect:');
1618                 } else {
1619                         if(!in_array('Expect:', $headers)) {
1620                                 array_push($headers, 'Expect:');
1621                         }
1622                 }
1623         }
1624         if($headers)
1625                 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
1626
1627         $check_cert = get_config('system','verifyssl');
1628         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, (($check_cert) ? true : false));
1629         $prx = get_config('system','proxy');
1630         if(strlen($prx)) {
1631                 curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
1632                 curl_setopt($ch, CURLOPT_PROXY, $prx);
1633                 $prxusr = get_config('system','proxyuser');
1634                 if(strlen($prxusr))
1635                         curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr);
1636         }
1637
1638         $a->set_curl_code(0);
1639
1640         // don't let curl abort the entire application
1641         // if it throws any errors.
1642
1643         $s = @curl_exec($ch);
1644
1645         $base = $s;
1646         $curl_info = curl_getinfo($ch);
1647         $http_code = $curl_info['http_code'];
1648
1649         $header = '';
1650
1651         // Pull out multiple headers, e.g. proxy and continuation headers
1652         // allow for HTTP/2.x without fixing code
1653
1654         while(preg_match('/^HTTP\/[1-2].+? [1-5][0-9][0-9]/',$base)) {
1655                 $chunk = substr($base,0,strpos($base,"\r\n\r\n")+4);
1656                 $header .= $chunk;
1657                 $base = substr($base,strlen($chunk));
1658         }
1659
1660         if($http_code == 301 || $http_code == 302 || $http_code == 303) {
1661         $matches = array();
1662         preg_match('/(Location:|URI:)(.*?)\n/', $header, $matches);
1663         $url = trim(array_pop($matches));
1664         $url_parsed = @parse_url($url);
1665         if (isset($url_parsed)) {
1666             $redirects++;
1667             return delete_url($url,$headers,$redirects,$timeout);
1668         }
1669     }
1670         $a->set_curl_code($http_code);
1671         $body = substr($s,strlen($header));
1672
1673         $a->set_curl_headers($header);
1674
1675         curl_close($ch);
1676         return($body);
1677 }}