]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/FacebookBridge/FacebookBridgePlugin.php
Merge remote-tracking branch 'upstream/master' into social-master
[quix0rs-gnu-social.git] / plugins / FacebookBridge / FacebookBridgePlugin.php
1 <?php
2 /**
3  * StatusNet - the distributed open-source microblogging tool
4  * Copyright (C) 2010-2011, StatusNet, Inc.
5  *
6  * A plugin for integrating Facebook with StatusNet. Includes single-sign-on
7  * and publishing notices to Facebook using Facebook's Graph API.
8  *
9  * PHP version 5
10  *
11  * This program is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Affero General Public License as published by
13  * the Free Software Foundation, either version 3 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU Affero General Public License for more details.
20  *
21  * You should have received a copy of the GNU Affero General Public License
22  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
23  *
24  * @category  Plugin
25  * @package   StatusNet
26  * @author    Zach Copley <zach@status.net>
27  * @copyright 2011 StatusNet, Inc.
28  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
29  * @link      http://status.net/
30  */
31
32 if (!defined('STATUSNET')) {
33     exit(1);
34 }
35
36 define("FACEBOOK_SERVICE", 2);
37
38 /**
39  * Main class for Facebook Bridge plugin
40  *
41  * @category  Plugin
42  * @package   StatusNet
43  * @author    Zach Copley <zach@status.net>
44  * @copyright 2010-2011 StatusNet, Inc.
45  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
46  * @link      http://status.net/
47  */
48 class FacebookBridgePlugin extends Plugin
49 {
50     public $appId;  // Facebook application ID
51     public $secret; // Facebook application secret
52
53     public $facebook = null; // Facebook application instance
54     public $dir      = null; // Facebook plugin dir
55
56     /**
57      * Initializer for this plugin
58      *
59      * Gets an instance of the Facebook API client object
60      *
61      * @return boolean hook value; true means continue processing, false means stop.
62      */
63     function initialize()
64     {
65
66         // Allow the id and key to be passed in
67         // Control panel will override
68
69         if (isset($this->appId)) {
70             $appId = common_config('facebook', 'appid');
71             if (empty($appId)) {
72                 Config::save(
73                     'facebook',
74                     'appid',
75                     $this->appId
76                 );
77             }
78         }
79
80         if (isset($this->secret)) {
81             $secret = common_config('facebook', 'secret');
82             if (empty($secret)) {
83                 Config::save('facebook', 'secret', $this->secret);
84             }
85         }
86
87         $this->facebook = Facebookclient::getFacebook(
88             $this->appId,
89             $this->secret
90         );
91
92         return true;
93     }
94
95     /**
96      * Load related modules when needed
97      *
98      * @param string $cls Name of the class to be loaded
99      *
100      * @return boolean hook value; true means continue processing, false means stop.
101      */
102     function onAutoload($cls)
103     {
104         $dir = dirname(__FILE__);
105
106         switch ($cls)
107         {
108         case 'Facebook': // Facebook PHP SDK
109             include_once $dir . '/extlib/base_facebook.php';
110             include_once $dir . '/extlib/facebook.php';
111             return false;
112         }
113
114         return parent::onAutoload($cls);
115     }
116
117     /**
118      * Database schema setup
119      *
120      * We maintain a table mapping StatusNet notices to Facebook items
121      *
122      * @see Schema
123      * @see ColumnDef
124      *
125      * @return boolean hook value; true means continue processing, false means stop.
126      */
127     function onCheckSchema()
128     {
129         $schema = Schema::get();
130         $schema->ensureTable('notice_to_item', Notice_to_item::schemaDef());
131         return true;
132     }
133
134     /*
135      * Does this $action need the Facebook JavaScripts?
136      */
137     function needsScripts($action)
138     {
139         static $needy = array(
140             'FacebookloginAction',
141             'FacebookfinishloginAction',
142             'FacebookadminpanelAction',
143             'FacebooksettingsAction'
144         );
145
146         if (in_array(get_class($action), $needy)) {
147             return true;
148         } else {
149             return false;
150         }
151     }
152
153     /**
154      * Map URLs to actions
155      *
156      * @param URLMapper $m path-to-action mapper
157      *
158      * @return boolean hook value; true means continue processing, false means stop.
159      */
160     public function onRouterInitialized(URLMapper $m)
161     {
162         // Always add the admin panel route
163         $m->connect('panel/facebook', array('action' => 'facebookadminpanel'));
164
165         $m->connect(
166             'main/facebooklogin',
167             array('action' => 'facebooklogin')
168         );
169         $m->connect(
170             'main/facebookfinishlogin',
171             array('action' => 'facebookfinishlogin')
172         );
173         $m->connect(
174             'settings/facebook',
175             array('action' => 'facebooksettings')
176         );
177         $m->connect(
178             'facebook/deauthorize',
179             array('action' => 'facebookdeauthorize')
180         );
181
182         return true;
183     }
184
185     /*
186      * Add a login tab for Facebook, but only if there's a Facebook
187      * application defined for the plugin to use.
188      *
189      * @param Action $action the current action
190      *
191      * @return void
192      */
193     function onEndLoginGroupNav($action)
194     {
195         $action_name = $action->trimmed('action');
196
197         if ($this->hasApplication()) {
198
199             $action->menuItem(
200                 // TRANS: Menu item for "Facebook" login.
201                 common_local_url('facebooklogin'),
202                 _m('MENU', 'Facebook'),
203                 // TRANS: Menu title for "Facebook" login.
204                 _m('Login or register using Facebook.'),
205                'facebooklogin' === $action_name
206             );
207         }
208
209         return true;
210     }
211
212     /**
213      * If the plugin's installed, this should be accessible to admins
214      */
215     function onAdminPanelCheck($name, &$isOK)
216     {
217         if ($name == 'facebook') {
218             $isOK = true;
219             return false;
220         }
221
222         return true;
223     }
224
225     /**
226      * Add a Facebook tab to the admin panels
227      *
228      * @param Widget $nav Admin panel nav
229      *
230      * @return boolean hook value
231      */
232     function onEndAdminPanelNav(Menu $nav)
233     {
234         if (AdminPanelAction::canAdmin('facebook')) {
235
236             $action_name = $nav->action->trimmed('action');
237
238             $nav->out->menuItem(
239                 common_local_url('facebookadminpanel'),
240                 // TRANS: Menu item for "Facebook" in administration panel.
241                 _m('MENU','Facebook'),
242                 // TRANS: Menu title for "Facebook" in administration panel.
243                 _m('Facebook integration configuration.'),
244                 $action_name == 'facebookadminpanel',
245                 'nav_facebook_admin_panel'
246             );
247         }
248
249         return true;
250     }
251
252     /*
253      * Add a tab for user-level Facebook settings if the user
254      * has a link to Facebook
255      *
256      * @param Action $action the current action
257      *
258      * @return void
259      */
260     function onEndConnectSettingsNav($action)
261     {
262         if ($this->hasApplication()) {
263             $action_name = $action->trimmed('action');
264
265             $user = common_current_user();
266
267             $flink = null;
268
269             if (!empty($user)) {
270                 $flink = Foreign_link::getByUserID(
271                     $user->id,
272                     FACEBOOK_SERVICE
273                 );
274             }
275
276             if (!empty($flink)) {
277
278                 $action->menuItem(
279                     common_local_url('facebooksettings'),
280                     // TRANS: Menu item for "Facebook" in user settings.
281                     _m('MENU','Facebook'),
282                     // TRANS: Menu title for "Facebook" in user settings.
283                     _m('Facebook settings.'),
284                     $action_name === 'facebooksettings'
285                 );
286             }
287         }
288     }
289
290     /*
291      * Is there a Facebook application for the plugin to use?
292      *
293      * Checks to see if a Facebook application ID and secret
294      * have been configured and a valid Facebook API client
295      * object exists.
296      *
297      */
298     function hasApplication()
299     {
300         if (!empty($this->facebook)) {
301
302             $appId  = $this->facebook->getAppId();
303             $secret = $this->facebook->getApiSecret();
304
305             if (!empty($appId) && !empty($secret)) {
306                 return true;
307             }
308         }
309
310         return false;
311     }
312
313     /*
314      * Output a Facebook div for the Facebook JavaSsript SDK to use
315      *
316      * @param Action $action the current action
317      *
318      */
319     function onStartShowHeader($action)
320     {
321         // output <div id="fb-root"></div> as close to <body> as possible
322         $action->element('div', array('id' => 'fb-root'));
323         return true;
324     }
325
326     /*
327      * Load the Facebook JavaScript SDK on pages that need them.
328      *
329      * @param Action $action the current action
330      *
331      */
332     function onEndShowScripts(Action $action)
333     {
334         if ($this->needsScripts($action)) {
335
336             $action->script('https://connect.facebook.net/en_US/all.js');
337
338             $script = <<<ENDOFSCRIPT
339 function setCookie(name, value) {
340     var date = new Date();
341     date.setTime(date.getTime() + (5 * 60 * 1000)); // 5 mins
342     var expires = "; expires=" + date.toGMTString();
343     document.cookie = name + "=" + value + expires + "; path=/";
344 }
345
346 FB.init({appId: %1\$s, status: true, cookie: true, xfbml: true, oauth: true});
347
348 $('#facebook_button').bind('click', function(event) {
349
350     event.preventDefault();
351
352     FB.login(function(response) {
353         if (response.authResponse) {
354             // put the access token in a cookie for the next step
355             setCookie('fb_access_token', response.authResponse.accessToken);
356             window.location.href = '%2\$s';
357         } else {
358             // NOP (user cancelled login)
359         }
360     }, {scope:'read_stream,publish_stream,offline_access,user_status,user_location,user_website,email'});
361 });
362 ENDOFSCRIPT;
363
364             $action->inlineScript(
365                 sprintf(
366                     $script,
367                     json_encode($this->facebook->getAppId()),
368                     common_local_url('facebookfinishlogin')
369                 )
370             );
371         }
372     }
373
374     /*
375      * Log the user out of Facebook, per the Facebook authentication guide
376      *
377      * @param Action action the current action
378      */
379     function onStartLogout($action)
380     {
381         if ($this->hasApplication()) {
382
383             $cur = common_current_user();
384             $flink = Foreign_link::getByUserID($cur->id, FACEBOOK_SERVICE);
385
386             if (!empty($flink)) {
387
388                 $this->facebook->setAccessToken($flink->credentials);
389
390                 if (common_config('singleuser', 'enabled')) {
391                     $user = User::singleUser();
392
393                     $destination = common_local_url(
394                         'showstream',
395                         array('nickname' => $user->nickname)
396                     );
397                 } else {
398                     $destination = common_local_url('public');
399                 }
400
401                 $logoutUrl = $this->facebook->getLogoutUrl(
402                     array('next' => $destination)
403                 );
404
405                 common_log(
406                     LOG_INFO,
407                     sprintf(
408                         "Logging user out of Facebook (fbuid = %s)",
409                         $fbuid
410                     ),
411                     __FILE__
412                 );
413
414                 $action->logout();
415
416                 common_redirect($logoutUrl, 303);
417             }
418
419             return true;
420         }
421     }
422
423     /*
424      * Add fbml namespace to our HTML, so Facebook's JavaScript SDK can parse
425      * and render XFBML tags
426      *
427      * @param Action    $action   the current action
428      * @param array     $attrs    array of attributes for the HTML tag
429      *
430      * @return nothing
431      */
432     function onStartHtmlElement($action, $attrs) {
433
434         if ($this->needsScripts($action)) {
435             $attrs = array_merge(
436                 $attrs,
437                 array('xmlns:fb' => 'http://www.facebook.com/2008/fbml')
438             );
439         }
440
441         return true;
442     }
443
444     /**
445      * Add a Facebook queue item for each notice
446      *
447      * @param Notice $notice      the notice
448      * @param array  &$transports the list of transports (queues)
449      *
450      * @return boolean hook return
451      */
452     function onStartEnqueueNotice(Notice $notice, array &$transports)
453     {
454         if (self::hasApplication() && $notice->isLocal() && $notice->inScope(null)) {
455             array_push($transports, 'facebook');
456         }
457         return true;
458     }
459
460     /**
461      * Register Facebook notice queue handler
462      *
463      * @param QueueManager $manager
464      *
465      * @return boolean hook return
466      */
467     function onEndInitializeQueueManager(QueueManager $manager)
468     {
469         if (self::hasApplication()) {
470             $manager->connect('facebook', 'FacebookQueueHandler');
471         }
472         return true;
473     }
474
475     /*
476      * Use SSL for Facebook stuff
477      *
478      * @param string $action name
479      * @param boolean $ssl outval to force SSL
480      * @return mixed hook return value
481      */
482     function onSensitiveAction($action, &$ssl)
483     {
484         $sensitive = array(
485             'facebookadminpanel',
486             'facebooksettings',
487             'facebooklogin',
488             'facebookfinishlogin'
489         );
490
491         if (in_array($action, $sensitive)) {
492             $ssl = true;
493             return false;
494         } else {
495             return true;
496         }
497     }
498
499     /**
500      * If a notice gets deleted, remove the Notice_to_item mapping and
501      * delete the item on Facebook
502      *
503      * @param User   $user   The user doing the deleting
504      * @param Notice $notice The notice getting deleted
505      *
506      * @return boolean hook value
507      */
508     function onStartDeleteOwnNotice(User $user, Notice $notice)
509     {
510         $client = new Facebookclient($notice);
511         $client->streamRemove();
512
513         return true;
514     }
515
516     /**
517      * Notify remote users when their notices get favorited.
518      *
519      * @param Profile or User $profile of local user doing the faving
520      * @param Notice $notice being favored
521      * @return hook return value
522      */
523     function onEndFavorNotice(Profile $profile, Notice $notice)
524     {
525         $client = new Facebookclient($notice, $profile);
526         $client->like();
527
528         return true;
529     }
530
531     /**
532      * Notify remote users when their notices get de-favorited.
533      *
534      * @param Profile $profile Profile person doing the de-faving
535      * @param Notice  $notice  Notice being favored
536      *
537      * @return hook return value
538      */
539     function onEndDisfavorNotice(Profile $profile, Notice $notice)
540     {
541         $client = new Facebookclient($notice, $profile);
542         $client->unLike();
543
544         return true;
545     }
546
547     /**
548      * Add links in the user's profile block to their Facebook profile URL.
549      *
550      * @param Profile $profile The profile being shown
551      * @param Array   &$links  Writeable array of arrays (href, text, image).
552      *
553      * @return boolean hook value (true)
554      */
555
556     function onOtherAccountProfiles($profile, &$links)
557     {
558         $fuser = null;
559
560         $flink = Foreign_link::getByUserID($profile->id, FACEBOOK_SERVICE);
561
562         if (!empty($flink)) {
563
564             $fuser = $this->getFacebookUser($flink->foreign_id);
565
566             if (!empty($fuser)) {
567                 $links[] = array("href" => $fuser->link,
568                                  "text" => sprintf(_("%s on Facebook"), $fuser->name),
569                                  "image" => $this->path("images/f_logo.png"));
570             }
571         }
572
573         return true;
574     }
575
576     function getFacebookUser($id) {
577
578         $key = Cache::key(sprintf("FacebookBridgePlugin:userdata:%s", $id));
579
580         $c = Cache::instance();
581
582         if ($c) {
583             $obj = $c->get($key);
584             if ($obj) {
585                 return $obj;
586             }
587         }
588
589         $url = sprintf("https://graph.facebook.com/%s", $id);
590         $client = new HTTPClient();
591         $resp = $client->get($url);
592
593         if (!$resp->isOK()) {
594             return null;
595         }
596
597         $user = json_decode($resp->getBody());
598
599         if ($user->error) {
600             return null;
601         }
602
603         if ($c) {
604             $c->set($key, $user);
605         }
606
607         return $user;
608     }
609
610     /*
611      * Add version info for this plugin
612      *
613      * @param array &$versions    plugin version descriptions
614      */
615     function onPluginVersion(array &$versions)
616     {
617         $versions[] = array(
618             'name' => 'Facebook Bridge',
619             'version' => GNUSOCIAL_VERSION,
620             'author' => 'Craig Andrews, Zach Copley',
621             'homepage' => 'http://status.net/wiki/Plugin:FacebookBridge',
622             'rawdescription' =>
623              // TRANS: Plugin description.
624             _m('A plugin for integrating StatusNet with Facebook.')
625         );
626
627         return true;
628     }
629 }