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