]> git.mxchange.org Git - friendica-addons.git/blob - facebook/facebook.php
Merge remote-tracking branch 'old/master' into mergeto
[friendica-addons.git] / facebook / facebook.php
1 <?php
2 /**
3  * Name: Facebook Connector
4  * Version: 1.0
5  * Author: Mike Macgirvin <http://macgirvin.com/profile/mike>
6  */
7
8 /**
9  * Installing the Friendica/Facebook connector
10  *
11  * 1. register an API key for your site from developer.facebook.com
12  *   a. We'd be very happy if you include "Friendica" in the application name
13  *      to increase name recognition. The Friendica icons are also present
14  *      in the images directory and may be uploaded as a Facebook app icon.
15  *      Use images/friendica-16.jpg for the Icon and images/friendica-128.jpg for the Logo.
16  *   b. The url should be your site URL with a trailing slash.
17  *      Friendica is a software application and does not require a Privacy Policy 
18  *      or Terms of Service, though your installation of it might. Facebook may require
19  *      that you provide a Privacy Policy, which we find ironic.  
20  *   c. Set the following values in your .htconfig.php file
21  *         $a->config['facebook']['appid'] = 'xxxxxxxxxxx';
22  *         $a->config['facebook']['appsecret'] = 'xxxxxxxxxxxxxxx';
23  *      Replace with the settings Facebook gives you.
24  *   d. Navigate to Set Web->Site URL & Domain -> Website Settings.  Set 
25  *      Site URL to yoursubdomain.yourdomain.com. Set Site Domain to your 
26  *      yourdomain.com.
27  * 2. Enable the facebook plugin by including it in .htconfig.php - e.g. 
28  *     $a->config['system']['addon'] = 'plugin1,plugin2,facebook';
29  * 3. Visit the Facebook Settings section of the "Settings->Plugin Settings" page.
30  *    and click 'Install Facebook Connector'.
31  * 4. This will ask you to login to Facebook and grant permission to the 
32  *    plugin to do its stuff. Allow it to do so. 
33  * 5. You're done. To turn it off visit the Plugin Settings page again and
34  *    'Remove Facebook posting'.
35  *
36  * Vidoes and embeds will not be posted if there is no other content. Links 
37  * and images will be converted to a format suitable for the Facebook API and 
38  * long posts truncated - with a link to view the full post. 
39  *
40  * Facebook contacts will not be able to view private photos, as they are not able to
41  * authenticate to your site to establish identity. We will address this 
42  * in a future release.
43  */
44
45 define('FACEBOOK_MAXPOSTLEN', 420);
46
47
48 function facebook_install() {
49         register_hook('post_local',       'addon/facebook/facebook.php', 'facebook_post_local');
50         register_hook('notifier_normal',  'addon/facebook/facebook.php', 'facebook_post_hook');
51         register_hook('jot_networks',     'addon/facebook/facebook.php', 'facebook_jot_nets');
52         register_hook('connector_settings',  'addon/facebook/facebook.php', 'facebook_plugin_settings');
53         register_hook('cron',             'addon/facebook/facebook.php', 'facebook_cron');
54         register_hook('queue_predeliver', 'addon/facebook/facebook.php', 'fb_queue_hook');
55 }
56
57
58 function facebook_uninstall() {
59         unregister_hook('post_local',       'addon/facebook/facebook.php', 'facebook_post_local');
60         unregister_hook('notifier_normal',  'addon/facebook/facebook.php', 'facebook_post_hook');
61         unregister_hook('jot_networks',     'addon/facebook/facebook.php', 'facebook_jot_nets');
62         unregister_hook('connector_settings',  'addon/facebook/facebook.php', 'facebook_plugin_settings');
63         unregister_hook('cron',             'addon/facebook/facebook.php', 'facebook_cron');
64         unregister_hook('queue_predeliver', 'addon/facebook/facebook.php', 'fb_queue_hook');
65
66         // hook moved
67         unregister_hook('post_local_end',  'addon/facebook/facebook.php', 'facebook_post_hook');
68         unregister_hook('plugin_settings',  'addon/facebook/facebook.php', 'facebook_plugin_settings');
69 }
70
71
72 /* declare the facebook_module function so that /facebook url requests will land here */
73
74 function facebook_module() {}
75
76
77
78 /* If a->argv[1] is a nickname, this is a callback from Facebook oauth requests. */
79
80 function facebook_init(&$a) {
81
82         if($a->argc != 2)
83                 return;
84         $nick = $a->argv[1];
85         if(strlen($nick))
86                 $r = q("SELECT `uid` FROM `user` WHERE `nickname` = '%s' LIMIT 1",
87                                 dbesc($nick)
88                 );
89         if(! count($r))
90                 return;
91
92         $uid           = $r[0]['uid'];
93         $auth_code     = (($_GET['code']) ? $_GET['code'] : '');
94         $error         = (($_GET['error_description']) ? $_GET['error_description'] : '');
95
96
97         if($error)
98                 logger('facebook_init: Error: ' . $error);
99
100         if($auth_code && $uid) {
101
102                 $appid = get_config('facebook','appid');
103                 $appsecret = get_config('facebook', 'appsecret');
104
105                 $x = fetch_url('https://graph.facebook.com/oauth/access_token?client_id='
106                         . $appid . '&client_secret=' . $appsecret . '&redirect_uri='
107                         . urlencode($a->get_baseurl() . '/facebook/' . $nick) 
108                         . '&code=' . $auth_code);
109
110                 logger('facebook_init: returned access token: ' . $x, LOGGER_DATA);
111
112                 if(strpos($x,'access_token=') !== false) {
113                         $token = str_replace('access_token=', '', $x);
114                         if(strpos($token,'&') !== false)
115                                 $token = substr($token,0,strpos($token,'&'));
116                         set_pconfig($uid,'facebook','access_token',$token);
117                         set_pconfig($uid,'facebook','post','1');
118                         if(get_pconfig($uid,'facebook','no_linking') === false)
119                                 set_pconfig($uid,'facebook','no_linking',1);
120                         fb_get_self($uid);
121                         fb_get_friends($uid);
122                         fb_consume_all($uid);
123
124                 }
125
126         }
127
128 }
129
130
131 function fb_get_self($uid) {
132         $access_token = get_pconfig($uid,'facebook','access_token');
133         if(! $access_token)
134                 return;
135         $s = fetch_url('https://graph.facebook.com/me/?access_token=' . $access_token);
136         if($s) {
137                 $j = json_decode($s);
138                 set_pconfig($uid,'facebook','self_id',(string) $j->id);
139         }
140 }
141
142
143
144 function fb_get_friends($uid) {
145
146         $r = q("SELECT `uid` FROM `user` WHERE `uid` = %d AND `account_expired` = 0 LIMIT 1",
147                 intval($uid)
148         );
149         if(! count($r))
150                 return;
151
152         $access_token = get_pconfig($uid,'facebook','access_token');
153
154         $no_linking = get_pconfig($uid,'facebook','no_linking');
155         if($no_linking)
156                 return;
157
158         if(! $access_token)
159                 return;
160         $s = fetch_url('https://graph.facebook.com/me/friends?access_token=' . $access_token);
161         if($s) {
162                 logger('facebook: fb_get_friends: ' . $s, LOGGER_DATA);
163                 $j = json_decode($s);
164                 logger('facebook: fb_get_friends: json: ' . print_r($j,true), LOGGER_DATA);
165                 if(! $j->data)
166                         return;
167                 foreach($j->data as $person) {
168                         $s = fetch_url('https://graph.facebook.com/' . $person->id . '?access_token=' . $access_token);
169                         if($s) {
170                                 $jp = json_decode($s);
171                                 logger('fb_get_friends: info: ' . print_r($jp,true), LOGGER_DATA);
172
173                                 // always use numeric link for consistency
174
175                                 $jp->link = 'http://facebook.com/profile.php?id=' . $person->id;
176
177                                 // check if we already have a contact
178
179                                 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `url` = '%s' LIMIT 1",
180                                         intval($uid),
181                                         dbesc($jp->link)
182                                 );                      
183
184                                 if(count($r)) {
185
186                                         // check that we have all the photos, this has been known to fail on occasion
187
188                                         if((! $r[0]['photo']) || (! $r[0]['thumb']) || (! $r[0]['micro'])) {  
189                                                 require_once("Photo.php");
190
191                                                 $photos = import_profile_photo('https://graph.facebook.com/' . $jp->id . '/picture', $uid, $r[0]['id']);
192
193                                                 $r = q("UPDATE `contact` SET `photo` = '%s', 
194                                                         `thumb` = '%s',
195                                                         `micro` = '%s', 
196                                                         `name-date` = '%s', 
197                                                         `uri-date` = '%s', 
198                                                         `avatar-date` = '%s'
199                                                         WHERE `id` = %d LIMIT 1
200                                                 ",
201                                                         dbesc($photos[0]),
202                                                         dbesc($photos[1]),
203                                                         dbesc($photos[2]),
204                                                         dbesc(datetime_convert()),
205                                                         dbesc(datetime_convert()),
206                                                         dbesc(datetime_convert()),
207                                                         intval($r[0]['id'])
208                                                 );                      
209                                         }       
210                                         continue;
211                                 }
212                                 else {
213
214                                         // create contact record 
215                                         $r = q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `addr`, `alias`, `notify`, `poll`, 
216                                                 `name`, `nick`, `photo`, `network`, `rel`, `priority`,
217                                                 `writable`, `blocked`, `readonly`, `pending` )
218                                                 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, 0, 0, 0 ) ",
219                                                 intval($uid),
220                                                 dbesc(datetime_convert()),
221                                                 dbesc($jp->link),
222                                                 dbesc(normalise_link($jp->link)),
223                                                 dbesc(''),
224                                                 dbesc(''),
225                                                 dbesc($jp->id),
226                                                 dbesc('facebook ' . $jp->id),
227                                                 dbesc($jp->name),
228                                                 dbesc(($jp->nickname) ? $jp->nickname : strtolower($jp->first_name)),
229                                                 dbesc('https://graph.facebook.com/' . $jp->id . '/picture'),
230                                                 dbesc(NETWORK_FACEBOOK),
231                                                 intval(CONTACT_IS_FRIEND),
232                                                 intval(1),
233                                                 intval(1)
234                                         );
235                                 }
236
237                                 $r = q("SELECT * FROM `contact` WHERE `url` = '%s' AND `uid` = %d LIMIT 1",
238                                         dbesc($jp->link),
239                                         intval($uid)
240                                 );
241
242                                 if(! count($r)) {
243                                         continue;
244                                 }
245
246                                 $contact = $r[0];
247                                 $contact_id  = $r[0]['id'];
248
249                                 require_once("Photo.php");
250
251                                 $photos = import_profile_photo($r[0]['photo'],$uid,$contact_id);
252
253                                 $r = q("UPDATE `contact` SET `photo` = '%s', 
254                                         `thumb` = '%s',
255                                         `micro` = '%s', 
256                                         `name-date` = '%s', 
257                                         `uri-date` = '%s', 
258                                         `avatar-date` = '%s'
259                                         WHERE `id` = %d LIMIT 1
260                                 ",
261                                         dbesc($photos[0]),
262                                         dbesc($photos[1]),
263                                         dbesc($photos[2]),
264                                         dbesc(datetime_convert()),
265                                         dbesc(datetime_convert()),
266                                         dbesc(datetime_convert()),
267                                         intval($contact_id)
268                                 );                      
269
270                         }
271                 }
272         }
273 }
274
275 // This is the POST method to the facebook settings page
276 // Content is posted to Facebook in the function facebook_post_hook() 
277
278 function facebook_post(&$a) {
279
280         $uid = local_user();
281         if($uid){
282
283                 $value = ((x($_POST,'post_by_default')) ? intval($_POST['post_by_default']) : 0);
284                 set_pconfig($uid,'facebook','post_by_default', $value);
285
286                 $no_linking = get_pconfig($uid,'facebook','no_linking');
287
288                 $no_wall = ((x($_POST,'facebook_no_wall')) ? intval($_POST['facebook_no_wall']) : 0);
289                 set_pconfig($uid,'facebook','no_wall',$no_wall);
290
291                 $private_wall = ((x($_POST,'facebook_private_wall')) ? intval($_POST['facebook_private_wall']) : 0);
292                 set_pconfig($uid,'facebook','private_wall',$private_wall);
293         
294
295                 set_pconfig($uid,'facebook','blocked_apps',escape_tags(trim($_POST['blocked_apps'])));
296
297                 $linkvalue = ((x($_POST,'facebook_linking')) ? intval($_POST['facebook_linking']) : 0);
298                 set_pconfig($uid,'facebook','no_linking', (($linkvalue) ? 0 : 1));
299
300                 // FB linkage was allowed but has just been turned off - remove all FB contacts and posts
301
302                 if((! intval($no_linking)) && (! intval($linkvalue))) {
303                         $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `network` = '%s' ",
304                                 intval($uid),
305                                 dbesc(NETWORK_FACEBOOK)
306                         );
307                         if(count($r)) {
308                                 require_once('include/Contact.php');
309                                 foreach($r as $rr)
310                                         contact_remove($rr['id']);
311                         }
312                 }
313                 elseif(intval($no_linking) && intval($linkvalue)) {
314                         // FB linkage is now allowed - import stuff.
315                         fb_get_self($uid);
316                         fb_get_friends($uid);
317                         fb_consume_all($uid);
318                 }
319
320                 info( t('Settings updated.') . EOL);
321         } 
322
323         return;         
324 }
325
326 // Facebook settings form
327
328 function facebook_content(&$a) {
329
330         if(! local_user()) {
331                 notice( t('Permission denied.') . EOL);
332                 return '';
333         }
334
335         if($a->argc > 1 && $a->argv[1] === 'remove') {
336                 del_pconfig(local_user(),'facebook','post');
337                 info( t('Facebook disabled') . EOL);
338         }
339
340         if($a->argc > 1 && $a->argv[1] === 'friends') {
341                 fb_get_friends(local_user());
342                 info( t('Updating contacts') . EOL);
343         }
344
345
346         $fb_installed = get_pconfig(local_user(),'facebook','post');
347
348         $appid = get_config('facebook','appid');
349
350         if(! $appid) {
351                 notice( t('Facebook API key is missing.') . EOL);
352                 return '';
353         }
354
355         $a->page['htmlhead'] .= '<link rel="stylesheet" type="text/css" href="' 
356                 . $a->get_baseurl() . '/addon/facebook/facebook.css' . '" media="all" />' . "\r\n";
357
358         $o .= '<h3>' . t('Facebook Connect') . '</h3>';
359
360         if(! $fb_installed) { 
361                 $o .= '<div id="facebook-enable-wrapper">';
362
363                 $o .= '<a href="https://www.facebook.com/dialog/oauth?client_id=' . $appid . '&redirect_uri=' 
364                         . $a->get_baseurl() . '/facebook/' . $a->user['nickname'] . '&scope=publish_stream,read_stream,offline_access">' . t('Install Facebook connector for this account.') . '</a>';
365                 $o .= '</div>';
366         }
367
368         if($fb_installed) {
369                 $o .= '<div id="facebook-disable-wrapper">';
370
371                 $o .= '<a href="' . $a->get_baseurl() . '/facebook/remove' . '">' . t('Remove Facebook connector') . '</a></div>';
372
373                 $o .= '<div id="facebook-enable-wrapper">';
374
375                 $o .= '<a href="https://www.facebook.com/dialog/oauth?client_id=' . $appid . '&redirect_uri=' 
376                         . $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>';
377                 $o .= '</div>';
378         
379                 $o .= '<div id="facebook-post-default-form">';
380                 $o .= '<form action="facebook" method="post" >';
381                 $post_by_default = get_pconfig(local_user(),'facebook','post_by_default');
382                 $checked = (($post_by_default) ? ' checked="checked" ' : '');
383                 $o .= '<input type="checkbox" name="post_by_default" value="1"' . $checked . '/>' . ' ' . t('Post to Facebook by default') . EOL;
384
385                 $no_linking = get_pconfig(local_user(),'facebook','no_linking');
386                 $checked = (($no_linking) ? '' : ' checked="checked" ');
387                 $o .= '<input type="checkbox" name="facebook_linking" value="1"' . $checked . '/>' . ' ' . t('Link all your Facebook friends and conversations on this website') . EOL ;
388
389                 $o .= '<p>' . t('Facebook conversations consist of your <em>profile wall</em> and your friend <em>stream</em>.');
390                 $o .= ' ' . t('On this website, your Facebook friend stream is only visible to you.');
391                 $o .= ' ' . t('The following settings determine the privacy of your Facebook profile wall on this website.') . '</p>';
392
393                 $private_wall = get_pconfig(local_user(),'facebook','private_wall');
394                 $checked = (($private_wall) ? ' checked="checked" ' : '');
395                 $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 ;
396
397
398                 $no_wall = get_pconfig(local_user(),'facebook','no_wall');
399                 $checked = (($no_wall) ? ' checked="checked" ' : '');
400                 $o .= '<input type="checkbox" name="facebook_no_wall" value="1"' . $checked . '/>' . ' ' . t('Do not import your Facebook profile wall conversations') . EOL ;
401
402                 $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>';
403
404
405                 $blocked_apps = get_pconfig(local_user(),'facebook','blocked_apps');
406
407                 $o .= '<div><label id="blocked-apps-label" for="blocked-apps">' . t('Comma separated applications to ignore') . ' </label></div>';
408         $o .= '<div><textarea id="blocked-apps" name="blocked_apps" >' . htmlspecialchars($blocked_apps) . '</textarea></div>';
409
410                 $o .= '<input type="submit" name="submit" value="' . t('Submit') . '" /></form></div>';
411         }
412
413         return $o;
414 }
415
416
417
418 function facebook_cron($a,$b) {
419
420         $last = get_config('facebook','last_poll');
421         
422         $poll_interval = intval(get_config('facebook','poll_interval'));
423         if(! $poll_interval)
424                 $poll_interval = 3600;
425
426         if($last) {
427                 $next = $last + $poll_interval;
428                 if($next > time()) 
429                         return;
430         }
431
432         logger('facebook_cron');
433
434
435         // Find the FB users on this site and randomize in case one of them
436         // uses an obscene amount of memory. It may kill this queue run
437         // but hopefully we'll get a few others through on each run. 
438
439         $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'facebook' AND `k` = 'post' AND `v` = '1' ORDER BY RAND() ");
440         if(count($r)) {
441                 foreach($r as $rr) {
442                         if(get_pconfig($rr['uid'],'facebook','no_linking'))
443                                 continue;
444                         $ab = intval(get_config('system','account_abandon_days'));
445                         if($ab > 0) {
446                                 $z = q("SELECT `uid` FROM `user` WHERE `uid` = %d AND `login_date` > UTC_TIMESTAMP() - INTERVAL %d DAY LIMIT 1",
447                                         intval($rr['uid']),
448                                         intval($ab)
449                                 );
450                                 if(! count($z))
451                                         continue;
452                         }
453
454                         // check for new friends once a day
455                         $last_friend_check = get_pconfig($rr['uid'],'facebook','friend_check');
456                         if($last_friend_check) 
457                                 $next_friend_check = $last_friend_check + 86400;
458                         if($next_friend_check <= time()) {
459                                 fb_get_friends($rr['uid']);
460                                 set_pconfig($rr['uid'],'facebook','friend_check',time());
461                         }
462                         fb_consume_all($rr['uid']);
463                 }
464         }       
465
466         set_config('facebook','last_poll', time());
467
468 }
469
470
471
472 function facebook_plugin_settings(&$a,&$b) {
473
474         $b .= '<div class="settings-block">';
475         $b .= '<h3>' . t('Facebook') . '</h3>';
476         $b .= '<a href="facebook">' . t('Facebook Connector Settings') . '</a><br />';
477         $b .= '</div>';
478
479 }
480
481 function facebook_jot_nets(&$a,&$b) {
482         if(! local_user())
483                 return;
484
485         $fb_post = get_pconfig(local_user(),'facebook','post');
486         if(intval($fb_post) == 1) {
487                 $fb_defpost = get_pconfig(local_user(),'facebook','post_by_default');
488                 $selected = ((intval($fb_defpost) == 1) ? ' checked="checked" ' : '');
489                 $b .= '<div class="profile-jot-net"><input type="checkbox" name="facebook_enable"' . $selected . ' value="1" /> ' 
490                         . t('Post to Facebook') . '</div>';     
491         }
492 }
493
494
495 function facebook_post_hook(&$a,&$b) {
496
497
498         if($b['deleted'] || ($b['created'] !== $b['edited']))
499                 return;
500
501         /**
502          * Post to Facebook stream
503          */
504
505         require_once('include/group.php');
506
507         logger('Facebook post');
508
509         $reply = false;
510         $likes = false;
511
512         $toplevel = (($b['id'] == $b['parent']) ? true : false);
513
514
515         $linking = ((get_pconfig($b['uid'],'facebook','no_linking')) ? 0 : 1);
516
517         if((! $toplevel) && ($linking)) {
518                 $r = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
519                         intval($b['parent']),
520                         intval($b['uid'])
521                 );
522                 if(count($r) && substr($r[0]['uri'],0,4) === 'fb::')
523                         $reply = substr($r[0]['uri'],4);
524                 elseif(count($r) && substr($r[0]['extid'],0,4) === 'fb::')
525                         $reply = substr($r[0]['extid'],4);
526                 else
527                         return;
528
529                 $u = q("SELECT * FROM user where uid = %d limit 1",
530                         intval($b['uid'])
531                 );
532                 if(! count($u))
533                         return;
534
535                 // only accept comments from the item owner. Other contacts are unknown to FB.
536  
537                 if(! link_compare($b['author-link'], $a->get_baseurl() . '/profile/' . $u[0]['nickname']))
538                         return;
539                 
540
541                 logger('facebook reply id=' . $reply);
542         }
543
544         if(strstr($b['postopts'],'facebook') || ($b['private']) || ($reply)) {
545
546                 if($b['private'] && $reply === false) {
547                         $allow_people = expand_acl($b['allow_cid']);
548                         $allow_groups = expand_groups(expand_acl($b['allow_gid']));
549                         $deny_people  = expand_acl($b['deny_cid']);
550                         $deny_groups  = expand_groups(expand_acl($b['deny_gid']));
551
552                         $recipients = array_unique(array_merge($allow_people,$allow_groups));
553                         $deny = array_unique(array_merge($deny_people,$deny_groups));
554
555                         $allow_str = dbesc(implode(', ',$recipients));
556                         if($allow_str) {
557                                 $r = q("SELECT `notify` FROM `contact` WHERE `id` IN ( $allow_str ) AND `network` = 'face'"); 
558                                 $allow_arr = array();
559                                 if(count($r)) 
560                                         foreach($r as $rr)
561                                                 $allow_arr[] = $rr['notify'];
562                         }
563
564                         $deny_str = dbesc(implode(', ',$deny));
565                         if($deny_str) {
566                                 $r = q("SELECT `notify` FROM `contact` WHERE `id` IN ( $deny_str ) AND `network` = 'face'"); 
567                                 $deny_arr = array();
568                                 if(count($r)) 
569                                         foreach($r as $rr)
570                                                 $deny_arr[] = $rr['notify'];
571                         }
572
573                         if(count($deny_arr) && (! count($allow_arr))) {
574
575                                 // One or more FB folks were denied access but nobody on FB was specifically allowed access.
576                                 // This might cause the post to be open to public on Facebook, but only to selected members
577                                 // on another network. Since this could potentially leak a post to somebody who was denied, 
578                                 // we will skip posting it to Facebook with a slightly vague but relevant message that will 
579                                 // hopefully lead somebody to this code comment for a better explanation of what went wrong.
580
581                                 notice( t('Post to Facebook cancelled because of multi-network access permission conflict.') . EOL);
582                                 return;
583                         }
584
585
586                         // if it's a private message but no Facebook members are allowed or denied, skip Facebook post
587
588                         if((! count($allow_arr)) && (! count($deny_arr)))
589                                 return;
590                 }
591
592                 if($b['verb'] == ACTIVITY_LIKE)
593                         $likes = true;                          
594
595
596                 $appid  = get_config('facebook', 'appid'  );
597                 $secret = get_config('facebook', 'appsecret' );
598
599                 if($appid && $secret) {
600
601                         logger('facebook: have appid+secret');
602
603                         $fb_token  = get_pconfig($b['uid'],'facebook','access_token');
604
605
606                         // post to facebook if it's a public post and we've ticked the 'post to Facebook' box, 
607                         // or it's a private message with facebook participants
608                         // or it's a reply or likes action to an existing facebook post                 
609
610                         if($fb_token && ($toplevel || $b['private'] || $reply)) {
611                                 logger('facebook: able to post');
612                                 require_once('library/facebook.php');
613                                 require_once('include/bbcode.php');     
614
615                                 $msg = $b['body'];
616
617                                 logger('Facebook post: original msg=' . $msg, LOGGER_DATA);
618
619                                 // make links readable before we strip the code
620
621                                 // unless it's a dislike - just send the text as a comment
622
623                                 if($b['verb'] == ACTIVITY_DISLIKE)
624                                         $msg = trim(strip_tags(bbcode($msg)));
625
626                                 $search_str = $a->get_baseurl() . '/search';
627
628                                 if(preg_match("/\[url=(.*?)\](.*?)\[\/url\]/is",$msg,$matches)) {
629
630                                         // don't use hashtags for message link
631
632                                         if(strpos($matches[2],$search_str) === false) {
633                                                 $link = $matches[1];
634                                                 if(substr($matches[2],0,5) != '[img]')
635                                                         $linkname = $matches[2];
636                                         }
637                                 }
638
639                                 // strip tag links to avoid link clutter, this really should be 
640                                 // configurable because we're losing information
641
642                                 $msg = preg_replace("/\#\[url=(.*?)\](.*?)\[\/url\]/is",'#$2',$msg);
643
644                                 // provide the link separately for normal links
645                                 $msg = preg_replace("/\[url=(.*?)\](.*?)\[\/url\]/is",'$2 $1',$msg);
646
647                                 if(preg_match("/\[img\](.*?)\[\/img\]/is",$msg,$matches))
648                                         $image = $matches[1];
649
650                                 $msg = preg_replace("/\[img\](.*?)\[\/img\]/is", t('Image: ') . '$1', $msg);
651
652                                 if((strpos($link,z_root()) !== false) && (! $image))
653                                         $image = $a->get_baseurl() . '/images/friendica-64.jpg';
654
655                                 $msg = trim(strip_tags(bbcode($msg)));
656                                 $msg = html_entity_decode($msg,ENT_QUOTES,'UTF-8');
657
658                                 // add any attachments as text urls
659
660                             $arr = explode(',',$b['attach']);
661
662                             if(count($arr)) {
663                                         $msg .= "\n";
664                                 foreach($arr as $r) {
665                                 $matches = false;
666                                                 $cnt = preg_match('|\[attach\]href=\"(.*?)\" size=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"\[\/attach\]|',$r,$matches);
667                                                 if($cnt) {
668                                                         $msg .= $matches[1];
669                                                 }
670                                         }
671                                 }
672
673                                 if (strlen($msg) > FACEBOOK_MAXPOSTLEN) {
674                                         $shortlink = "";
675                                         require_once('library/slinky.php');
676
677                                         $display_url = $b['plink'];
678
679                                         $slinky = new Slinky( $display_url );
680                                         // setup a cascade of shortening services
681                                         // try to get a short link from these services
682                                         // in the order ur1.ca, trim, id.gd, tinyurl
683                                         $slinky->set_cascade( array( new Slinky_UR1ca(), new Slinky_Trim(), new Slinky_IsGd(), new Slinky_TinyURL() ) );
684                                         $shortlink = $slinky->short();
685                                         // the new message will be shortened such that "... $shortlink"
686                                         // will fit into the character limit
687                                         $msg = substr($msg, 0, FACEBOOK_MAXPOSTLEN - strlen($shortlink) - 4);
688                                         $msg .= '... ' . $shortlink;
689                                 }
690                                 if(! strlen($msg))
691                                         return;
692
693                                 logger('Facebook post: msg=' . $msg, LOGGER_DATA);
694
695                                 if($likes) { 
696                                         $postvars = array('access_token' => $fb_token);
697                                 }
698                                 else {
699                                         $postvars = array(
700                                                 'access_token' => $fb_token, 
701                                                 'message' => $msg
702                                         );
703                                         if(isset($image))
704                                                 $postvars['picture'] = $image;
705                                         if(isset($link))
706                                                 $postvars['link'] = $link;
707                                         if(isset($linkname))
708                                                 $postvars['name'] = $linkname;
709                                 }
710
711                                 if(($b['private']) && ($toplevel)) {
712                                         $postvars['privacy'] = '{"value": "CUSTOM", "friends": "SOME_FRIENDS"';
713                                         if(count($allow_arr))
714                                                 $postvars['privacy'] .= ',"allow": "' . implode(',',$allow_arr) . '"';
715                                         if(count($deny_arr))
716                                                 $postvars['privacy'] .= ',"deny": "' . implode(',',$deny_arr) . '"';
717                                         $postvars['privacy'] .= '}';
718
719                                 }
720
721                                 if($reply) {
722                                         $url = 'https://graph.facebook.com/' . $reply . '/' . (($likes) ? 'likes' : 'comments');
723                                 }
724                                 else { 
725                                         $url = 'https://graph.facebook.com/me/feed';
726                                         if($b['plink'])
727                                                 $postvars['actions'] = '{"name": "' . t('View on Friendica') . '", "link": "' .  $b['plink'] . '"}';
728                                 }
729
730                                 logger('facebook: post to ' . $url);
731                                 logger('facebook: postvars: ' . print_r($postvars,true));
732
733                                 // "test_mode" prevents anything from actually being posted.
734                                 // Otherwise, let's do it. 
735
736                                 if(! get_config('facebook','test_mode')) {
737                                         $x = post_url($url, $postvars);
738
739                                         $retj = json_decode($x);
740                                         if($retj->id) {
741                                                 q("UPDATE `item` SET `extid` = '%s' WHERE `id` = %d LIMIT 1",
742                                                         dbesc('fb::' . $retj->id),
743                                                         intval($b['id'])
744                                                 );
745                                         }
746                                         else {
747                                                 if(! $likes) {
748                                                         $s = serialize(array('url' => $url, 'item' => $b['id'], 'post' => $postvars));
749                                                         require_once('include/queue_fn.php');
750                                                         add_to_queue($a->contact,NETWORK_FACEBOOK,$s);
751                                                         notice( t('Facebook post failed. Queued for retry.') . EOL);
752                                                 }
753                                         }
754                                         
755                                         logger('Facebook post returns: ' . $x, LOGGER_DEBUG);
756                                 }
757                         }
758                 }
759         }
760 }
761
762
763 function facebook_post_local(&$a,&$b) {
764
765         // Figure out if Facebook posting is enabled for this post and file it in 'postopts'
766         // where we will discover it during background delivery.
767
768         // This can only be triggered by a local user posting to their own wall.
769
770         if((local_user()) && (local_user() == $b['uid'])) {
771
772                 $fb_post   = intval(get_pconfig(local_user(),'facebook','post'));
773                 $fb_enable = (($fb_post && x($_REQUEST,'facebook_enable')) ? intval($_REQUEST['facebook_enable']) : 0);
774
775                 // if API is used, default to the chosen settings
776                 if($_REQUEST['api_source'] && intval(get_pconfig(local_user(),'facebook','post_by_default')))
777                         $fb_enable = 1;
778
779                 if(! $fb_enable)
780                         return;
781
782                 if(strlen($b['postopts']))
783                         $b['postopts'] .= ',';
784                 $b['postopts'] .= 'facebook';
785         }
786 }
787
788
789 function fb_queue_hook(&$a,&$b) {
790
791         $qi = q("SELECT * FROM `queue` WHERE `network` = '%s'",
792                 dbesc(NETWORK_FACEBOOK)
793         );
794         if(! count($qi))
795                 return;
796
797         require_once('include/queue_fn.php');
798
799         foreach($qi as $x) {
800                 if($x['network'] !== NETWORK_FACEBOOK)
801                         continue;
802
803                 logger('facebook_queue: run');
804
805                 $r = q("SELECT `user`.* FROM `user` LEFT JOIN `contact` on `contact`.`uid` = `user`.`uid` 
806                         WHERE `contact`.`self` = 1 AND `contact`.`id` = %d LIMIT 1",
807                         intval($x['cid'])
808                 );
809                 if(! count($r))
810                         continue;
811
812                 $user = $r[0];
813
814                 $appid  = get_config('facebook', 'appid'  );
815                 $secret = get_config('facebook', 'appsecret' );
816
817                 if($appid && $secret) {
818                         $fb_post   = intval(get_pconfig($user['uid'],'facebook','post'));
819                         $fb_token  = get_pconfig($user['uid'],'facebook','access_token');
820
821                         if($fb_post && $fb_token) {
822                                 logger('facebook_queue: able to post');
823                                 require_once('library/facebook.php');
824
825                                 $z = unserialize($x['content']);
826                                 $item = $z['item'];
827                                 $j = post_url($z['url'],$z['post']);
828
829                                 $retj = json_decode($j);
830                                 if($retj->id) {
831                                         q("UPDATE `item` SET `extid` = '%s' WHERE `id` = %d LIMIT 1",
832                                                 dbesc('fb::' . $retj->id),
833                                                 intval($item)
834                                         );
835                                         logger('facebook_queue: success: ' . $j); 
836                                         remove_queue_item($x['id']);
837                                 }
838                                 else {
839                                         logger('facebook_queue: failed: ' . $j);
840                                         update_queue_time($x['id']);
841                                 }
842                         }
843                 }
844         }
845 }
846
847 function fb_consume_all($uid) {
848
849         require_once('include/items.php');
850
851         $access_token = get_pconfig($uid,'facebook','access_token');
852         if(! $access_token)
853                 return;
854         
855         if(! get_pconfig($uid,'facebook','no_wall')) {
856                 $private_wall = intval(get_pconfig($uid,'facebook','private_wall'));
857                 $s = fetch_url('https://graph.facebook.com/me/feed?access_token=' . $access_token);
858                 if($s) {
859                         $j = json_decode($s);
860                         logger('fb_consume_stream: wall: ' . print_r($j,true), LOGGER_DATA);
861                         fb_consume_stream($uid,$j,($private_wall) ? false : true);
862                 }
863         }
864         $s = fetch_url('https://graph.facebook.com/me/home?access_token=' . $access_token);
865         if($s) {
866                 $j = json_decode($s);
867                 logger('fb_consume_stream: feed: ' . print_r($j,true), LOGGER_DATA);
868                 fb_consume_stream($uid,$j,false);
869         }
870
871 }
872
873 function fb_get_photo($uid,$link) {
874         $access_token = get_pconfig($uid,'facebook','access_token');
875         if(! $access_token || (! stristr($link,'facebook.com/photo.php')))
876                 return "\n" . '[url=' . $link . ']' . t('link') . '[/url]';
877         $ret = preg_match('/fbid=([0-9]*)/',$link,$match);
878         if($ret)
879                 $photo_id = $match[1];
880         $x = fetch_url('https://graph.facebook.com/' . $photo_id . '?access_token=' . $access_token);
881         $j = json_decode($x);
882         if($j->picture)
883                 return "\n\n" . '[url=' . $link . '][img]' . $j->picture . '[/img][/url]';
884         else
885                 return "\n" . '[url=' . $link . ']' . t('link') . '[/url]';
886 }
887
888 function fb_consume_stream($uid,$j,$wall = false) {
889
890         $a = get_app();
891
892
893         $user = q("SELECT `nickname`, `blockwall` FROM `user` WHERE `uid` = %d AND `account_expired` = 0 LIMIT 1",
894                 intval($uid)
895         );
896         if(! count($user))
897                 return;
898
899         $my_local_url = $a->get_baseurl() . '/profile/' . $user[0]['nickname'];
900
901         $no_linking = get_pconfig($uid,'facebook','no_linking');
902         if($no_linking)
903                 return;
904
905         $self = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
906                 intval($uid)
907         );
908
909         $blocked_apps = get_pconfig($uid,'facebook','blocked_apps');
910         $blocked_apps_arr = explode(',',$blocked_apps);
911
912         $self_id = get_pconfig($uid,'facebook','self_id');
913         if(! count($j->data) || (! strlen($self_id)))
914                 return;
915
916         foreach($j->data as $entry) {
917                 logger('fb_consume: entry: ' . print_r($entry,true), LOGGER_DATA);
918                 $datarray = array();
919
920                 $r = q("SELECT * FROM `item` WHERE ( `uri` = '%s' OR `extid` = '%s') AND `uid` = %d LIMIT 1",
921                                 dbesc('fb::' . $entry->id),
922                                 dbesc('fb::' . $entry->id),
923                                 intval($uid)
924                 );
925                 if(count($r)) {
926                         $post_exists = true;
927                         $orig_post = $r[0];
928                         $top_item = $r[0]['id'];
929                 }
930                 else {
931                         $post_exists = false;
932                         $orig_post = null;
933                 }
934
935                 if(! $orig_post) {
936                         $datarray['gravity'] = 0;
937                         $datarray['uid'] = $uid;
938                         $datarray['wall'] = (($wall) ? 1 : 0);
939                         $datarray['uri'] = $datarray['parent-uri'] = 'fb::' . $entry->id;
940                         $from = $entry->from;
941                         if($from->id == $self_id)
942                                 $datarray['contact-id'] = $self[0]['id'];
943                         else {
944                                 $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d AND `blocked` = 0 AND `readonly` = 0 LIMIT 1",
945                                         dbesc($from->id),
946                                         intval($uid)
947                                 );
948                                 if(count($r))
949                                         $datarray['contact-id'] = $r[0]['id'];
950                         }
951
952                         // don't store post if we don't have a contact
953
954                         if(! x($datarray,'contact-id')) {
955                                 logger('no contact: post ignored');
956                                 continue; 
957                         }
958
959                         $datarray['verb'] = ACTIVITY_POST;                                              
960                         if($wall) {
961                                 $datarray['owner-name'] = $self[0]['name'];
962                                 $datarray['owner-link'] = $self[0]['url'];
963                                 $datarray['owner-avatar'] = $self[0]['thumb'];
964                         }
965                         if(isset($entry->application) && isset($entry->application->name) && strlen($entry->application->name))
966                                 $datarray['app'] = strip_tags($entry->application->name);
967                         else
968                                 $datarray['app'] = 'facebook';
969
970                         $found_blocked = false;
971
972                         if(count($blocked_apps_arr)) {
973                                 foreach($blocked_apps_arr as $bad_appl) {
974                                         if(strlen(trim($bad_appl)) && (stristr($datarray['app'],trim($bad_appl)))) {
975                                                 $found_blocked = true;
976                                         }
977                                 }
978                         }
979                                 
980                         if($found_blocked) {
981                                 logger('facebook: blocking application: ' . $datarray['app']);
982                                 continue;
983                         }
984
985                         $datarray['author-name'] = $from->name;
986                         $datarray['author-link'] = 'http://facebook.com/profile.php?id=' . $from->id;
987                         $datarray['author-avatar'] = 'https://graph.facebook.com/' . $from->id . '/picture';
988                         $datarray['plink'] = $datarray['author-link'] . '&v=wall&story_fbid=' . substr($entry->id,strpos($entry->id,'_') + 1);
989
990                         $datarray['body'] = escape_tags($entry->message);
991
992                         if($entry->picture && $entry->link) {
993                                 $datarray['body'] .= "\n\n" . '[url=' . $entry->link . '][img]' . $entry->picture . '[/img][/url]';
994                         }
995                         else {
996                                 if($entry->picture)
997                                         $datarray['body'] .= "\n\n" . '[img]' . $entry->picture . '[/img]';
998                                 // if just a link, it may be a wall photo - check
999                                 if($entry->link)
1000                                         $datarray['body'] .= fb_get_photo($uid,$entry->link);
1001                         }
1002                         if($entry->name)
1003                                 $datarray['body'] .= "\n" . $entry->name;
1004                         if($entry->caption)
1005                                 $datarray['body'] .= "\n" . $entry->caption;
1006                         if($entry->description)
1007                                 $datarray['body'] .= "\n" . $entry->description;
1008                         $datarray['created'] = datetime_convert('UTC','UTC',$entry->created_time);
1009                         $datarray['edited'] = datetime_convert('UTC','UTC',$entry->updated_time);
1010
1011                         // If the entry has a privacy policy, we cannot assume who can or cannot see it,
1012                         // as the identities are from a foreign system. Mark it as private to the owner.
1013
1014                         if($entry->privacy && $entry->privacy->value !== 'EVERYONE') {
1015                                 $datarray['private'] = 1;
1016                                 $datarray['allow_cid'] = '<' . $uid . '>';
1017                         }
1018                         
1019                         $top_item = item_store($datarray);
1020                         $r = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1021                                 intval($top_item),
1022                                 intval($uid)
1023                         );                      
1024                         if(count($r)) {
1025                                 $orig_post = $r[0];
1026                                 logger('fb: new top level item posted');
1027                         }
1028                 }
1029
1030                 if(isset($entry->likes) && isset($entry->likes->data))
1031                         $likers = $entry->likes->data;
1032                 else
1033                         $likers = null;
1034
1035                 if(isset($entry->comments) && isset($entry->comments->data))
1036                         $comments = $entry->comments->data;
1037                 else
1038                         $comments = null;
1039
1040                 if(is_array($likers)) {
1041                         foreach($likers as $likes) {
1042
1043                                 if(! $orig_post)
1044                                         continue;
1045
1046                                 // If we posted the like locally, it will be found with our url, not the FB url.
1047
1048                                 $second_url = (($likes->id == $self_id) ? $self[0]['url'] : 'http://facebook.com/profile.php?id=' . $likes->id); 
1049
1050                                 $r = q("SELECT * FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `verb` = '%s' 
1051                                         AND ( `author-link` = '%s' OR `author-link` = '%s' ) LIMIT 1",
1052                                         dbesc($orig_post['uri']),
1053                                         intval($uid),
1054                                         dbesc(ACTIVITY_LIKE),
1055                                         dbesc('http://facebook.com/profile.php?id=' . $likes->id),
1056                                         dbesc($second_url)
1057                                 );
1058
1059                                 if(count($r))
1060                                         continue;
1061                                         
1062                                 $likedata = array();
1063                                 $likedata['parent'] = $top_item;
1064                                 $likedata['verb'] = ACTIVITY_LIKE;
1065                                 $likedata['gravity'] = 3;
1066                                 $likedata['uid'] = $uid;
1067                                 $likedata['wall'] = (($wall) ? 1 : 0);
1068                                 $likedata['uri'] = item_new_uri($a->get_baseurl(), $uid);
1069                                 $likedata['parent-uri'] = $orig_post['uri'];
1070                                 if($likes->id == $self_id)
1071                                         $likedata['contact-id'] = $self[0]['id'];
1072                                 else {
1073                                         $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d AND `blocked` = 0 AND `readonly` = 0 LIMIT 1",
1074                                                 dbesc($likes->id),
1075                                                 intval($uid)
1076                                         );
1077                                         if(count($r))
1078                                                 $likedata['contact-id'] = $r[0]['id'];
1079                                 }
1080                                 if(! x($likedata,'contact-id'))
1081                                         $likedata['contact-id'] = $orig_post['contact-id'];
1082
1083                                 $likedata['app'] = 'facebook';
1084                                 $likedata['verb'] = ACTIVITY_LIKE;                                              
1085                                 $likedata['author-name'] = $likes->name;
1086                                 $likedata['author-link'] = 'http://facebook.com/profile.php?id=' . $likes->id;
1087                                 $likedata['author-avatar'] = 'https://graph.facebook.com/' . $likes->id . '/picture';
1088                                 
1089                                 $author  = '[url=' . $likedata['author-link'] . ']' . $likedata['author-name'] . '[/url]';
1090                                 $objauthor =  '[url=' . $orig_post['author-link'] . ']' . $orig_post['author-name'] . '[/url]';
1091                                 $post_type = t('status');
1092                         $plink = '[url=' . $orig_post['plink'] . ']' . $post_type . '[/url]';
1093                                 $likedata['object-type'] = ACTIVITY_OBJ_NOTE;
1094
1095                                 $likedata['body'] = sprintf( t('%1$s likes %2$s\'s %3$s'), $author, $objauthor, $plink);
1096                                 $likedata['object'] = '<object><type>' . ACTIVITY_OBJ_NOTE . '</type><local>1</local>' . 
1097                                         '<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>';  
1098
1099                                 $item = item_store($likedata);                  
1100                         }
1101                 }
1102                 if(is_array($comments)) {
1103                         foreach($comments as $cmnt) {
1104
1105                                 if(! $orig_post)
1106                                         continue;
1107
1108                                 $r = q("SELECT * FROM `item` WHERE `uid` = %d AND ( `uri` = '%s' OR `extid` = '%s' ) LIMIT 1",
1109                                         intval($uid),
1110                                         dbesc('fb::' . $cmnt->id),
1111                                         dbesc('fb::' . $cmnt->id)
1112                                 );
1113                                 if(count($r))
1114                                         continue;
1115
1116                                 $cmntdata = array();
1117                                 $cmntdata['parent'] = $top_item;
1118                                 $cmntdata['verb'] = ACTIVITY_POST;
1119                                 $cmntdata['gravity'] = 6;
1120                                 $cmntdata['uid'] = $uid;
1121                                 $cmntdata['wall'] = (($wall) ? 1 : 0);
1122                                 $cmntdata['uri'] = 'fb::' . $cmnt->id;
1123                                 $cmntdata['parent-uri'] = $orig_post['uri'];
1124                                 if($cmnt->from->id == $self_id) {
1125                                         $cmntdata['contact-id'] = $self[0]['id'];
1126                                 }
1127                                 else {
1128                                         $r = q("SELECT * FROM `contact` WHERE `notify` = '%s' AND `uid` = %d LIMIT 1",
1129                                                 dbesc($cmnt->from->id),
1130                                                 intval($uid)
1131                                         );
1132                                         if(count($r)) {
1133                                                 $cmntdata['contact-id'] = $r[0]['id'];
1134                                                 if($r[0]['blocked'] || $r[0]['readonly'])
1135                                                         continue;
1136                                         }
1137                                 }
1138                                 if(! x($cmntdata,'contact-id'))
1139                                         $cmntdata['contact-id'] = $orig_post['contact-id'];
1140
1141                                 $cmntdata['app'] = 'facebook';
1142                                 $cmntdata['created'] = datetime_convert('UTC','UTC',$cmnt->created_time);
1143                                 $cmntdata['edited']  = datetime_convert('UTC','UTC',$cmnt->created_time);
1144                                 $cmntdata['verb'] = ACTIVITY_POST;                                              
1145                                 $cmntdata['author-name'] = $cmnt->from->name;
1146                                 $cmntdata['author-link'] = 'http://facebook.com/profile.php?id=' . $cmnt->from->id;
1147                                 $cmntdata['author-avatar'] = 'https://graph.facebook.com/' . $cmnt->from->id . '/picture';
1148                                 $cmntdata['body'] = $cmnt->message;
1149                                 $item = item_store($cmntdata);                  
1150                         }
1151                 }
1152         }
1153 }
1154