]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/FacebookBridge/FacebookBridgePlugin.php
Facebook bridge mostly working again with new OAuth 2.0 flow
[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         //common_debug("class = " . $cls);
107
108         switch ($cls)
109         {
110         case 'Facebook': // Facebook PHP SDK
111             include_once $dir . '/extlib/base_facebook.php';
112             include_once $dir . '/extlib/facebook.php';
113             return false;
114         case 'FacebookloginAction':
115         case 'FacebookfinishloginAction':
116         case 'FacebookadminpanelAction':
117         case 'FacebooksettingsAction':
118         case 'FacebookdeauthorizeAction':
119             include_once $dir . '/actions/' . strtolower(mb_substr($cls, 0, -6)) . '.php';
120             return false;
121         case 'Facebookclient':
122         case 'FacebookQueueHandler':
123             include_once $dir . '/lib/' . strtolower($cls) . '.php';
124             return false;
125         case 'Notice_to_item':
126             include_once $dir . '/classes/' . $cls . '.php';
127             return false;
128         default:
129             return true;
130         }
131     }
132
133     /**
134      * Database schema setup
135      *
136      * We maintain a table mapping StatusNet notices to Facebook items
137      *
138      * @see Schema
139      * @see ColumnDef
140      *
141      * @return boolean hook value; true means continue processing, false means stop.
142      */
143     function onCheckSchema()
144     {
145         $schema = Schema::get();
146         $schema->ensureTable('notice_to_item', Notice_to_item::schemaDef());
147         return true;
148     }
149
150     /*
151      * Does this $action need the Facebook JavaScripts?
152      */
153     function needsScripts($action)
154     {
155         static $needy = array(
156             'FacebookloginAction',
157             'FacebookfinishloginAction',
158             'FacebookadminpanelAction',
159             'FacebooksettingsAction'
160         );
161
162         if (in_array(get_class($action), $needy)) {
163             return true;
164         } else {
165             return false;
166         }
167     }
168
169     /**
170      * Map URLs to actions
171      *
172      * @param Net_URL_Mapper $m path-to-action mapper
173      *
174      * @return boolean hook value; true means continue processing, false means stop.
175      */
176     function onRouterInitialized($m)
177     {
178         // Always add the admin panel route
179         $m->connect('panel/facebook', array('action' => 'facebookadminpanel'));
180
181         $m->connect(
182             'main/facebooklogin',
183             array('action' => 'facebooklogin')
184         );
185         $m->connect(
186             'main/facebookfinishlogin',
187             array('action' => 'facebookfinishlogin')
188         );
189         $m->connect(
190             'settings/facebook',
191             array('action' => 'facebooksettings')
192         );
193         $m->connect(
194             'facebook/deauthorize',
195             array('action' => 'facebookdeauthorize')
196         );
197
198         return true;
199     }
200
201     /*
202      * Add a login tab for Facebook, but only if there's a Facebook
203      * application defined for the plugin to use.
204      *
205      * @param Action $action the current action
206      *
207      * @return void
208      */
209     function onEndLoginGroupNav($action)
210     {
211         $action_name = $action->trimmed('action');
212
213         if ($this->hasApplication()) {
214
215             $action->menuItem(
216                 // TRANS: Menu item for "Facebook" login.
217                 common_local_url('facebooklogin'),
218                 _m('MENU', 'Facebook'),
219                 // TRANS: Menu title for "Facebook" login.
220                 _m('Login or register using Facebook.'),
221                'facebooklogin' === $action_name
222             );
223         }
224
225         return true;
226     }
227
228     /**
229      * If the plugin's installed, this should be accessible to admins
230      */
231     function onAdminPanelCheck($name, &$isOK)
232     {
233         if ($name == 'facebook') {
234             $isOK = true;
235             return false;
236         }
237
238         return true;
239     }
240
241     /**
242      * Add a Facebook tab to the admin panels
243      *
244      * @param Widget $nav Admin panel nav
245      *
246      * @return boolean hook value
247      */
248     function onEndAdminPanelNav($nav)
249     {
250         if (AdminPanelAction::canAdmin('facebook')) {
251
252             $action_name = $nav->action->trimmed('action');
253
254             $nav->out->menuItem(
255                 common_local_url('facebookadminpanel'),
256                 // TRANS: Menu item for "Facebook" in administration panel.
257                 _m('MENU','Facebook'),
258                 // TRANS: Menu title for "Facebook" in administration panel.
259                 _m('Facebook integration configuration.'),
260                 $action_name == 'facebookadminpanel',
261                 'nav_facebook_admin_panel'
262             );
263         }
264
265         return true;
266     }
267
268     /*
269      * Add a tab for user-level Facebook settings if the user
270      * has a link to Facebook
271      *
272      * @param Action $action the current action
273      *
274      * @return void
275      */
276     function onEndConnectSettingsNav($action)
277     {
278         if ($this->hasApplication()) {
279             $action_name = $action->trimmed('action');
280
281             $user = common_current_user();
282
283             $flink = null;
284
285             if (!empty($user)) {
286                 $flink = Foreign_link::getByUserID(
287                     $user->id,
288                     FACEBOOK_SERVICE
289                 );
290             }
291
292             if (!empty($flink)) {
293
294                 $action->menuItem(
295                     common_local_url('facebooksettings'),
296                     // TRANS: Menu item for "Facebook" in user settings.
297                     _m('MENU','Facebook'),
298                     // TRANS: Menu title for "Facebook" in user settings.
299                     _m('Facebook settings.'),
300                     $action_name === 'facebooksettings'
301                 );
302             }
303         }
304     }
305
306     /*
307      * Is there a Facebook application for the plugin to use?
308      *
309      * Checks to see if a Facebook application ID and secret
310      * have been configured and a valid Facebook API client
311      * object exists.
312      *
313      */
314     function hasApplication()
315     {
316         if (!empty($this->facebook)) {
317
318             $appId  = $this->facebook->getAppId();
319             $secret = $this->facebook->getApiSecret();
320
321             if (!empty($appId) && !empty($secret)) {
322                 return true;
323             }
324         }
325
326         return false;
327     }
328
329     /*
330      * Output a Facebook div for the Facebook JavaSsript SDK to use
331      *
332      * @param Action $action the current action
333      *
334      */
335     function onStartShowHeader($action)
336     {
337         // output <div id="fb-root"></div> as close to <body> as possible
338         $action->element('div', array('id' => 'fb-root'));
339         return true;
340     }
341
342     /*
343      * Load the Facebook JavaScript SDK on pages that need them.
344      *
345      * @param Action $action the current action
346      *
347      */
348     function onEndShowScripts($action)
349     {
350         if ($this->needsScripts($action)) {
351
352             $action->script('https://connect.facebook.net/en_US/all.js');
353
354             $script = <<<ENDOFSCRIPT
355 FB.init({appId: %1\$s, status: true, cookie: true, xfbml: true, oauth: true});
356
357 $('#facebook_button').bind('click', function(event) {
358
359     event.preventDefault();
360
361     FB.login(function(response) {
362         if (response.authResponse) {
363             window.location.href = '%2\$s';
364         } else {
365             // NOP (user cancelled login)
366         }
367     }, {scope:'read_stream,publish_stream,offline_access,user_status,user_location,user_website,email'});
368 });
369 ENDOFSCRIPT;
370
371             $action->inlineScript(
372                 sprintf(
373                     $script,
374                     json_encode($this->facebook->getAppId()),
375                     common_local_url('facebookfinishlogin')
376                 )
377             );
378         }
379     }
380
381     /*
382      * Log the user out of Facebook, per the Facebook authentication guide
383      *
384      * @param Action action the current action
385      */
386     function onEndLogout($action)
387     {
388         if ($this->hasApplication()) {
389             //$session = $this->facebook->getSession();
390             $fbuser  = null;
391             $fbuid   = null;
392
393                 try {
394                     $fbuid  = $this->facebook->getUser();
395                     $fbuser = $this->facebook->api('/me');
396                  } catch (FacebookApiException $e) {
397                      common_log(LOG_ERROR, $e, __FILE__);
398                  }
399
400             if (!empty($fbuser)) {
401
402                 $logoutUrl = $this->facebook->getLogoutUrl(
403                     array('next' => common_local_url('public'))
404                 );
405
406                 common_log(
407                     LOG_INFO,
408                     sprintf(
409                         "Logging user out of Facebook (fbuid = %s)",
410                         $fbuid
411                     ),
412                     __FILE__
413                 );
414                 common_debug("LOGOUT URL = $logoutUrl");
415                 common_redirect($logoutUrl, 303);
416             }
417         }
418     }
419
420     /*
421      * Add fbml namespace to our HTML, so Facebook's JavaScript SDK can parse
422      * and render XFBML tags
423      *
424      * @param Action    $action   the current action
425      * @param array     $attrs    array of attributes for the HTML tag
426      *
427      * @return nothing
428      */
429     function onStartHtmlElement($action, $attrs) {
430
431         if ($this->needsScripts($action)) {
432             $attrs = array_merge(
433                 $attrs,
434                 array('xmlns:fb' => 'http://www.facebook.com/2008/fbml')
435             );
436         }
437
438         return true;
439     }
440
441     /**
442      * Add a Facebook queue item for each notice
443      *
444      * @param Notice $notice      the notice
445      * @param array  &$transports the list of transports (queues)
446      *
447      * @return boolean hook return
448      */
449     function onStartEnqueueNotice($notice, &$transports)
450     {
451         if (self::hasApplication() && $notice->isLocal() && $notice->inScope(null)) {
452             array_push($transports, 'facebook');
453         }
454         return true;
455     }
456
457     /**
458      * Register Facebook notice queue handler
459      *
460      * @param QueueManager $manager
461      *
462      * @return boolean hook return
463      */
464     function onEndInitializeQueueManager($manager)
465     {
466         if (self::hasApplication()) {
467             $manager->connect('facebook', 'FacebookQueueHandler');
468         }
469         return true;
470     }
471
472     /*
473      * Use SSL for Facebook stuff
474      *
475      * @param string $action name
476      * @param boolean $ssl outval to force SSL
477      * @return mixed hook return value
478      */
479     function onSensitiveAction($action, &$ssl)
480     {
481         $sensitive = array(
482             'facebookadminpanel',
483             'facebooksettings',
484             'facebooklogin',
485             'facebookfinishlogin'
486         );
487
488         if (in_array($action, $sensitive)) {
489             $ssl = true;
490             return false;
491         } else {
492             return true;
493         }
494     }
495
496     /**
497      * If a notice gets deleted, remove the Notice_to_item mapping and
498      * delete the item on Facebook
499      *
500      * @param User   $user   The user doing the deleting
501      * @param Notice $notice The notice getting deleted
502      *
503      * @return boolean hook value
504      */
505     function onStartDeleteOwnNotice(User $user, Notice $notice)
506     {
507         $client = new Facebookclient($notice);
508         $client->streamRemove();
509
510         return true;
511     }
512
513     /**
514      * Notify remote users when their notices get favorited.
515      *
516      * @param Profile or User $profile of local user doing the faving
517      * @param Notice $notice being favored
518      * @return hook return value
519      */
520     function onEndFavorNotice(Profile $profile, Notice $notice)
521     {
522         $client = new Facebookclient($notice, $profile);
523         $client->like();
524
525         return true;
526     }
527
528     /**
529      * Notify remote users when their notices get de-favorited.
530      *
531      * @param Profile $profile Profile person doing the de-faving
532      * @param Notice  $notice  Notice being favored
533      *
534      * @return hook return value
535      */
536     function onEndDisfavorNotice(Profile $profile, Notice $notice)
537     {
538         $client = new Facebookclient($notice, $profile);
539         $client->unLike();
540
541         return true;
542     }
543
544     /*
545      * Add version info for this plugin
546      *
547      * @param array &$versions    plugin version descriptions
548      */
549     function onPluginVersion(&$versions)
550     {
551         $versions[] = array(
552             'name' => 'Facebook Bridge',
553             'version' => STATUSNET_VERSION,
554             'author' => 'Craig Andrews, Zach Copley',
555             'homepage' => 'http://status.net/wiki/Plugin:FacebookBridge',
556             'rawdescription' =>
557              // TRANS: Plugin description.
558             _m('A plugin for integrating StatusNet with Facebook.')
559         );
560
561         return true;
562     }
563 }